{"thread":{"id":"52936","subject":"[PATCH 0/7] New execute-commands hook for centralized workflow","startedAt":"2020-03-04T11:34:07Z","lastAt":"2021-02-13T15:14:19Z","messageCount":266,"participants":["Jiang Xin","Junio C Hamano","Jeff King","Johannes Schindelin","SZEDER Gábor","Johannes Sixt"],"isPatch":true,"patchVersion":1,"patchTotal":7},"messages":[{"id":"392841","messageId":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":null,"subject":"[PATCH 0/7] New execute-commands hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-04T11:33:05Z","receivedAt":"2020-03-04T11:34:07Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"In my planned speech at the Git Merge 2020 conference, this series of\npatches is a core part of AGit-Flow (a centralized workflow like\nGerrit). Due to a coronavirus outbreak in China, I was unable to attend\nthe meeting. I wrote a blog \"AGit-Flow and git-repo\" based on my planned\nspeech, see:\n\n    https://git-repo.info/en/2020/03/agit-flow-and-git-repo/ \n\nGit calls an internal `execute_commands` function to handle commands\nsent from the client to `git-receive-pack`.  Regardless of what\nreferences the user pushes, git creates or updates the corresponding\nreferences if the user has write permission.  A contributor who has\nno write permission cannot push to repository directly.  So, the\ncontributor has to write commits to an alternate location and sends\npull request by emails or by other ways.  We call this distributed\nworkflow.\n\nIt would be more convenient to work in a centralized workflow like what\nGerrit provided for some cases.  For example, a read-only user may run\nthe following `git push` command to push commits to a special reference\nto create a code review, instead of updating a reference directly.\n\n    git push -o reviewers=user1,user2 \\\n        -o oldoid=89c082363ac950d224a7259bfba3ccfbf4c560c4 \\\n        origin \\\n        HEAD:refs/for/<branch-name>/<session>\n\nThe `<branch-name>` in the above example can be as simple as \"master\",\nor a more complicated branch name like \"foo/bar\".  The `<session>` in\nthe above example command can be the local branch name of the clien-\nside, such as \"my/topic\".\n\nTo support this kind of workflow in CGit, add a filter and a new\nhandler.  The filter will check the prefix of the reference name, and\nif the command has a special reference name, the filter will add a\nspecific tag (`exec_by_hook`) to the command.  Commands with this\nspecific tag will be executed by a new handler (an external hook named\n\"execute-commands\") instead of the internal `execute_commands` function.\n\nWe can use the external \"execute-commands\" hook to create pull requests\nor send emails.  The centralized workflow is not a replacement for\nthe distributed workflow, but a supplement. Especially for lightweight\ncode contribution or for working on a project with multiple repositories.\n\nWe also implement a command line tool for this kind of workflow, see:\n\n    https://github.com/aliyun/git-repo-go\n\n\nJiang Xin (7):\n  receive-pack: new external execute-commands hook\n  receive-pack: feed all commands to post-receive\n  receive-pack: try `execute-commands --pre-receive`\n  receive-pack: read env from execute-commands output\n  refs.c: refactor to reuse ref_is_hidden()\n  receive-pack: new config receive.executeCommandsHookRefs\n  hook: add document and example for \"execute-commands\" hook\n\n Documentation/config/receive.txt         |  11 +\n Documentation/githooks.txt               |  43 ++\n builtin/receive-pack.c                   | 228 +++++++-\n refs.c                                   |  11 +-\n refs.h                                   |   1 +\n t/t5411-execute-commands-hook.sh         | 698 +++++++++++++++++++++++\n templates/hooks--execute-commands.sample | 131 +++++\n 7 files changed, 1093 insertions(+), 30 deletions(-)\n create mode 100755 t/t5411-execute-commands-hook.sh\n create mode 100755 templates/hooks--execute-commands.sample\n\n-- \n2.25.1.362.g51ebf55b93\n\n"},{"id":"392842","messageId":"20200304113312.34229-3-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH 2/7] receive-pack: feed all commands to post-receive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-04T11:33:07Z","receivedAt":"2020-03-04T11:34:11Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Feed all commands to \"post-receive\" hook, not only normal commands, but\nalso commands with \"exec_by_hook\" flag.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c           | 11 +++++++++--\n t/t5411-execute-commands-hook.sh |  8 ++++----\n 2 files changed, 13 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 24eb999ed4..c97abfbcd3 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -669,6 +669,7 @@ static void prepare_push_cert_sha1(struct child_process *proc)\n \n struct receive_hook_feed_state {\n \tstruct command *cmd;\n+\tint exec_all;\n \tint exec_by_hook;\n \tint hook_must_exist;\n \tint skip_broken;\n@@ -761,6 +762,8 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n \twhile (cmd)\n \t\tif (state->skip_broken && (cmd->error_string || cmd->did_not_exist))\n \t\t\tcmd = cmd->next;\n+\t\telse if (state->exec_all)\n+\t\t       break;\n \t\telse if (state->exec_by_hook && !cmd->exec_by_hook)\n \t\t\tcmd = cmd->next;\n \t\telse if (!state->exec_by_hook && cmd->exec_by_hook)\n@@ -784,6 +787,7 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n static int run_receive_hook(struct command *commands,\n \t\t\t    const char *hook_name,\n \t\t\t    int skip_broken,\n+\t\t\t    int exec_all,\n \t\t\t    const struct string_list *push_options)\n {\n \tstruct receive_hook_feed_state state;\n@@ -791,6 +795,7 @@ static int run_receive_hook(struct command *commands,\n \n \tstrbuf_init(&state.buf, 0);\n \tstate.cmd = commands;\n+\tstate.exec_all = exec_all;\n \tstate.exec_by_hook = 0;\n \tstate.hook_must_exist = 0;\n \tstate.skip_broken = skip_broken;\n@@ -840,6 +845,7 @@ static int run_execute_commands_pre_receive_hook(struct command *commands,\n \n \tstrbuf_init(&state.buf, 0);\n \tstate.cmd = commands;\n+\tstate.exec_all = 0;\n \tstate.exec_by_hook = 1;\n \tstate.hook_must_exist = 0;\n \tstate.skip_broken = 0;\n@@ -861,6 +867,7 @@ static int run_execute_commands_hook(struct command *commands,\n \n \tstrbuf_init(&state.buf, 0);\n \tstate.cmd = commands;\n+\tstate.exec_all = 0;\n \tstate.exec_by_hook = 1;\n \tstate.hook_must_exist = 1;\n \tstate.skip_broken = 1;\n@@ -1575,7 +1582,7 @@ static void execute_commands(struct command *commands,\n \t}\n \n \tif (seen_internal_exec)\n-\t\tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n+\t\tif (run_receive_hook(commands, \"pre-receive\", 0, 0, push_options)) {\n \t\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n \t\t\t\tif (!cmd->error_string)\n \t\t\t\t\tcmd->error_string = \"pre-receive hook declined\";\n@@ -2114,7 +2121,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\t\tunlink_or_warn(pack_lockfile);\n \t\tif (report_status)\n \t\t\treport(commands, unpack_status);\n-\t\trun_receive_hook(commands, \"post-receive\", 1,\n+\t\trun_receive_hook(commands, \"post-receive\", 1, 1,\n \t\t\t\t &push_options);\n \t\trun_update_post_hook(commands);\n \t\tstring_list_clear(&push_options, 0);\ndiff --git a/t/t5411-execute-commands-hook.sh b/t/t5411-execute-commands-hook.sh\nindex 2ff0d5cbcd..b6444ca047 100755\n--- a/t/t5411-execute-commands-hook.sh\n+++ b/t/t5411-execute-commands-hook.sh\n@@ -125,7 +125,7 @@ test_expect_success \"create local topic branch\" '\n \t)\n '\n \n-test_expect_failure \"push one special ref: refs/for/master\" '\n+test_expect_success \"push one special ref: refs/for/master\" '\n \t(\n \t\tcd work &&\n \t\tgit update-ref HEAD $B &&\n@@ -182,7 +182,7 @@ test_expect_success \"add back the execute-commands hook\" '\n \tmv $bare/hooks/execute-commands.ok $bare/hooks/execute-commands\n '\n \n-test_expect_failure \"push one special ref: refs/for/a/b/c\" '\n+test_expect_success \"push one special ref: refs/for/a/b/c\" '\n \t(\n \t\tcd work &&\n \t\tgit push origin HEAD:refs/for/a/b/c/my/topic\n@@ -199,7 +199,7 @@ test_expect_failure \"push one special ref: refs/for/a/b/c\" '\n \ttest_cmp expect actual\n '\n \n-test_expect_failure \"push two special references\" '\n+test_expect_success \"push two special references\" '\n \t(\n \t\tcd work &&\n \t\tgit push origin \\\n@@ -300,7 +300,7 @@ test_expect_success \"restore execute-commands hook\" '\n \tmv $bare/hooks/execute-commands.ok $bare/hooks/execute-commands\n '\n \n-test_expect_failure \"push mixed references successfully\" '\n+test_expect_success \"push mixed references successfully\" '\n \t(\n \t\tcd work &&\n \t\tgit push origin \\\n-- \n2.25.1.362.g51ebf55b93\n\n"},{"id":"392843","messageId":"20200304113312.34229-2-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH 1/7] receive-pack: new external execute-commands hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-04T11:33:06Z","receivedAt":"2020-03-04T11:34:14Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Git calls an internal `execute_commands` function to handle commands\nsent from client to `git-receive-pack`.  Regardless of what references\nthe user pushes, git creates or updates the corresponding references\nif the user has write permission.  A contributor who has no write\npermission, cannot push to the the repository directly.  So, the\ncontributor has to write commits to an alternate location, and sends\npull request by emails or by other ways.  We call this distributed\nworkflow.\n\nIt would be more convenient to work in a centralized workflow like what\nGerrit provided for some cases.  For example, a read-only user may run\nthe following `git push` command to push commits to a special reference\nto create a code review, instead of updating a reference directly.\n\n    git push -o reviewers=user1,user2 \\\n        -o oldoid=89c082363ac950d224a7259bfba3ccfbf4c560c4 \\\n        origin \\\n        HEAD:refs/for/<branch-name>/<session>\n\nThe `<branch-name>` in the above example can be as simple as \"master\",\nor a more complicated branch name like \"foo/bar\".  The `<session>` in\nthe above example command can be the local branch name of the client\nside, such as \"my/topic\".\n\nIn order to support this kind of workflow in CGit, add a filter and a\nnew handler.  The filter will check the prefix of the reference name,\nand if the command has a special reference name, the filter will add a\nspecific tag (`exec_by_hook`) to the command.  Commands with this\nspecific tag will be executed by a new handler (an exeternal hook named\n\"execute-commands\") instead of the internal `execute_commands` function.\n\nThere are two phases involved to run \"execute-commands\" hook:\n\n* In order to check permissions for special `git-push` command, run a\n  new \"execute-commands--pre-receive\" hook (or `execute-commands\n  --pre-receive`, implemented in latter commit) instead of the\n  \"pre-receive\" hook, because adding a new hook won't break the old\n  implementation of the \"pre-receive\" hook.\n\n* Then will call the \"execute-commands\" hook (without any parameter).\n  This hook may call an external API to create a code review or send\n  emails.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c           | 135 ++++++++--\n t/t5411-execute-commands-hook.sh | 427 +++++++++++++++++++++++++++++++\n 2 files changed, 540 insertions(+), 22 deletions(-)\n create mode 100755 t/t5411-execute-commands-hook.sh\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 411e0b4d99..24eb999ed4 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -311,7 +311,8 @@ struct command {\n \tstruct command *next;\n \tconst char *error_string;\n \tunsigned int skip_update:1,\n-\t\t     did_not_exist:1;\n+\t\t     did_not_exist:1,\n+\t\t     exec_by_hook:1;\n \tint index;\n \tstruct object_id old_oid;\n \tstruct object_id new_oid;\n@@ -668,6 +669,8 @@ static void prepare_push_cert_sha1(struct child_process *proc)\n \n struct receive_hook_feed_state {\n \tstruct command *cmd;\n+\tint exec_by_hook;\n+\tint hook_must_exist;\n \tint skip_broken;\n \tstruct strbuf buf;\n \tconst struct string_list *push_options;\n@@ -683,8 +686,13 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n \tint code;\n \n \targv[0] = find_hook(hook_name);\n-\tif (!argv[0])\n-\t\treturn 0;\n+\tif (!argv[0]) {\n+\t\tif (feed_state->hook_must_exist) {\n+\t\t\trp_error(\"cannot to find hook '%s'\", hook_name);\n+\t\t\treturn 1;\n+\t\t} else\n+\t\t\treturn 0;\n+\t}\n \n \targv[1] = NULL;\n \n@@ -750,9 +758,15 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n \tstruct receive_hook_feed_state *state = state_;\n \tstruct command *cmd = state->cmd;\n \n-\twhile (cmd &&\n-\t       state->skip_broken && (cmd->error_string || cmd->did_not_exist))\n-\t\tcmd = cmd->next;\n+\twhile (cmd)\n+\t\tif (state->skip_broken && (cmd->error_string || cmd->did_not_exist))\n+\t\t\tcmd = cmd->next;\n+\t\telse if (state->exec_by_hook && !cmd->exec_by_hook)\n+\t\t\tcmd = cmd->next;\n+\t\telse if (!state->exec_by_hook && cmd->exec_by_hook)\n+\t\t\tcmd = cmd->next;\n+\t\telse\n+\t\t\tbreak;\n \tif (!cmd)\n \t\treturn -1; /* EOF */\n \tstrbuf_reset(&state->buf);\n@@ -777,6 +791,8 @@ static int run_receive_hook(struct command *commands,\n \n \tstrbuf_init(&state.buf, 0);\n \tstate.cmd = commands;\n+\tstate.exec_by_hook = 0;\n+\tstate.hook_must_exist = 0;\n \tstate.skip_broken = skip_broken;\n \tif (feed_receive_hook(&state, NULL, NULL))\n \t\treturn 0;\n@@ -816,14 +832,45 @@ static int run_update_hook(struct command *cmd)\n \treturn finish_command(&proc);\n }\n \n-static int is_ref_checked_out(const char *ref)\n+static int run_execute_commands_pre_receive_hook(struct command *commands,\n+\t\t\t    const struct string_list *push_options)\n {\n-\tif (is_bare_repository())\n+\tstruct receive_hook_feed_state state;\n+\tint status;\n+\n+\tstrbuf_init(&state.buf, 0);\n+\tstate.cmd = commands;\n+\tstate.exec_by_hook = 1;\n+\tstate.hook_must_exist = 0;\n+\tstate.skip_broken = 0;\n+\tif (feed_receive_hook(&state, NULL, NULL))\n \t\treturn 0;\n+\tstate.cmd = commands;\n+\tstate.push_options = push_options;\n+\tstatus = run_and_feed_hook(\"execute-commands--pre-receive\",\n+\t\t\tfeed_receive_hook, &state);\n+\tstrbuf_release(&state.buf);\n+\treturn status;\n+}\n+\n+static int run_execute_commands_hook(struct command *commands,\n+\t\t\t\t     const struct string_list *push_options)\n+{\n+\tstruct receive_hook_feed_state state;\n+\tint status;\n \n-\tif (!head_name)\n+\tstrbuf_init(&state.buf, 0);\n+\tstate.cmd = commands;\n+\tstate.exec_by_hook = 1;\n+\tstate.hook_must_exist = 1;\n+\tstate.skip_broken = 1;\n+\tif (feed_receive_hook(&state, NULL, NULL))\n \t\treturn 0;\n-\treturn !strcmp(head_name, ref);\n+\tstate.cmd = commands;\n+\tstate.push_options = push_options;\n+\tstatus = run_and_feed_hook(\"execute-commands\", feed_receive_hook, &state);\n+\tstrbuf_release(&state.buf);\n+\treturn status;\n }\n \n static char *refuse_unconfigured_deny_msg =\n@@ -1373,7 +1420,7 @@ static void warn_if_skipped_connectivity_check(struct command *commands,\n \tint checked_connectivity = 1;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (should_process_cmd(cmd) && si->shallow_ref[cmd->index]) {\n+\t\tif (should_process_cmd(cmd) && !cmd->exec_by_hook && si->shallow_ref[cmd->index]) {\n \t\t\terror(\"BUG: connectivity check has not been run on ref %s\",\n \t\t\t      cmd->ref_name);\n \t\t\tchecked_connectivity = 0;\n@@ -1390,7 +1437,7 @@ static void execute_commands_non_atomic(struct command *commands,\n \tstruct strbuf err = STRBUF_INIT;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->exec_by_hook)\n \t\t\tcontinue;\n \n \t\ttransaction = ref_transaction_begin(&err);\n@@ -1430,7 +1477,7 @@ static void execute_commands_atomic(struct command *commands,\n \t}\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->exec_by_hook)\n \t\t\tcontinue;\n \n \t\tcmd->error_string = update(cmd, si);\n@@ -1466,6 +1513,8 @@ static void execute_commands(struct command *commands,\n \tstruct iterate_data data;\n \tstruct async muxer;\n \tint err_fd = 0;\n+\tint seen_exec_by_hook = 0;\n+\tint seen_internal_exec = 0;\n \n \tif (unpacker_error) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n@@ -1495,14 +1544,45 @@ static void execute_commands(struct command *commands,\n \n \treject_updates_to_hidden(commands);\n \n-\tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n-\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\t\tif (!cmd->error_string)\n-\t\t\t\tcmd->error_string = \"pre-receive hook declined\";\n+\t/* Try to find commands that have special prefix, and will run these\n+\t * commands using an external \"execute-commands\" hook.\n+\t */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!should_process_cmd(cmd))\n+\t\t\tcontinue;\n+\n+\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n+\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n+\t\t\tcmd->exec_by_hook = 1;\n+\t\t\tseen_exec_by_hook = 1;\n+\t\t} else\n+\t\t\tseen_internal_exec = 1;\n+\t}\n+\n+\tif (seen_exec_by_hook) {\n+\t\t/* Try to find and run the `execute-commands--pre-receive` hook to check\n+\t\t * permissions on the special commands.\n+\t\t *\n+\t\t * If it does not exists, try to run `execute-commands --pre-receive`.\n+\t\t */\n+\t\tif (run_execute_commands_pre_receive_hook(commands, push_options)) {\n+\t\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\t\tif (!cmd->error_string)\n+\t\t\t\t\tcmd->error_string = \"execute-commands hook declined\";\n+\t\t\t}\n+\t\t\treturn;\n \t\t}\n-\t\treturn;\n \t}\n \n+\tif (seen_internal_exec)\n+\t\tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n+\t\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\t\tif (!cmd->error_string)\n+\t\t\t\t\tcmd->error_string = \"pre-receive hook declined\";\n+\t\t\t}\n+\t\t\treturn;\n+\t\t}\n+\n \t/*\n \t * Now we'll start writing out refs, which means the objects need\n \t * to be in their final positions so that other processes can see them.\n@@ -1521,10 +1601,21 @@ static void execute_commands(struct command *commands,\n \tfree(head_name_to_free);\n \thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, NULL, NULL);\n \n-\tif (use_atomic)\n-\t\texecute_commands_atomic(commands, si);\n-\telse\n-\t\texecute_commands_non_atomic(commands, si);\n+\tif (seen_exec_by_hook) {\n+\t\tif (run_execute_commands_hook(commands, push_options)) {\n+\t\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\t\tif (!cmd->error_string  && (cmd->exec_by_hook || use_atomic))\n+\t\t\t\t\tcmd->error_string = \"fail to run execute-commands hook\";\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (seen_internal_exec) {\n+\t\tif (use_atomic)\n+\t\t\texecute_commands_atomic(commands, si);\n+\t\telse\n+\t\t\texecute_commands_non_atomic(commands, si);\n+\t}\n \n \tif (shallow_update)\n \t\twarn_if_skipped_connectivity_check(commands, si);\ndiff --git a/t/t5411-execute-commands-hook.sh b/t/t5411-execute-commands-hook.sh\nnew file mode 100755\nindex 0000000000..2ff0d5cbcd\n--- /dev/null\n+++ b/t/t5411-execute-commands-hook.sh\n@@ -0,0 +1,427 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2018-2020 Jiang Xin\n+#\n+\n+test_description='Test execute-commands hook on special git-push refspec'\n+\n+. ./test-lib.sh\n+\n+bare=bare.git\n+\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{} 2>/dev/null)\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+test_expect_success setup '\n+\tgit init --bare $bare &&\n+\n+\t# Enable push options for bare.git.\n+\tgit -C $bare config receive.advertisePushOptions true &&\n+\n+\tgit clone --no-local $bare work &&\n+\tcreate_commits_in work A B\n+'\n+\n+test_expect_success \"setup hooks\" '\n+\t## execute-commands--pre-receive hook\n+\tcat >$bare/hooks/execute-commands--pre-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"execute: execute-commands--pre-receive\\n\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\tprintf >&2 \">> old: \\$old, new: \\$new, ref: \\$ref.\\n\"\n+\tdone\n+\tEOF\n+\n+\t## execute-commands hook\n+\tcat >$bare/hooks/execute-commands <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"execute: execute-commands\\n\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\tprintf >&2 \">> old: \\$old, new: \\$new, ref: \\$ref.\\n\"\n+\tdone\n+\tEOF\n+\n+\t## pre-receive hook\n+\tcat >$bare/hooks/pre-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"execute: pre-receive hook\\n\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\tprintf >&2 \">> old: \\$old, new: \\$new, ref: \\$ref.\\n\"\n+\tdone\n+\tEOF\n+\n+\t## post-receive hook\n+\tcat >$bare/hooks/post-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"execute: post-receive hook\\n\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\tprintf >&2 \">> old: \\$old, new: \\$new, ref: \\$ref.\\n\"\n+\tdone\n+\tEOF\n+\tchmod a+x \\\n+\t\t$bare/hooks/pre-receive \\\n+\t\t$bare/hooks/post-receive \\\n+\t\t$bare/hooks/execute-commands \\\n+\t\t$bare/hooks/execute-commands--pre-receive\n+'\n+\n+test_expect_success \"push normal branches and execute pre-receive and post-receive hooks\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit update-ref HEAD $A &&\n+\t\tgit push origin HEAD HEAD:maint 2>&1\n+\t) >out &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: 102939797ab91a4f201d131418d2c9d919dcdd2c, ref: refs/heads/master.\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: 102939797ab91a4f201d131418d2c9d919dcdd2c, ref: refs/heads/maint.\n+\tremote: execute: post-receive hook\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: 102939797ab91a4f201d131418d2c9d919dcdd2c, ref: refs/heads/master.\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: 102939797ab91a4f201d131418d2c9d919dcdd2c, ref: refs/heads/maint.\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"create local topic branch\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit checkout -b my/topic origin/master\n+\t)\n+'\n+\n+test_expect_failure \"push one special ref: refs/for/master\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit update-ref HEAD $B &&\n+\t\tgit push origin HEAD:refs/for/master/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: execute-commands--pre-receive\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/master/my/topic.\n+\tremote: execute: execute-commands\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/master/my/topic.\n+\tremote: execute: post-receive hook\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/master/my/topic.\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"remove execute-commands hook\" '\n+\tmv $bare/hooks/execute-commands $bare/hooks/execute-commands.ok\n+'\n+\n+test_expect_success \"push branch: refs/heads/a/b/c\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit update-ref HEAD $A &&\n+\t\tgit push origin HEAD:a/b/c 2>&1\n+\t) >out &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: 102939797ab91a4f201d131418d2c9d919dcdd2c, ref: refs/heads/a/b/c.\n+\tremote: execute: post-receive hook\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: 102939797ab91a4f201d131418d2c9d919dcdd2c, ref: refs/heads/a/b/c.\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"fail to push special ref: refs/for/master\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit update-ref HEAD $B &&\n+\t\ttest_must_fail git push origin HEAD:refs/for/master/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: execute-commands--pre-receive\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/master/my/topic.\n+\tremote: error: cannot to find hook '\"'\"'execute-commands'\"'\"'\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"add back the execute-commands hook\" '\n+\tmv $bare/hooks/execute-commands.ok $bare/hooks/execute-commands\n+'\n+\n+test_expect_failure \"push one special ref: refs/for/a/b/c\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit push origin HEAD:refs/for/a/b/c/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: execute-commands--pre-receive\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/a/b/c/my/topic.\n+\tremote: execute: execute-commands\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/a/b/c/my/topic.\n+\tremote: execute: post-receive hook\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/a/b/c/my/topic.\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_failure \"push two special references\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit push origin \\\n+\t\t\tHEAD:refs/for/maint/my/topic \\\n+\t\t\tHEAD:refs/for/a/b/c/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: execute-commands--pre-receive\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/a/b/c/my/topic.\n+\tremote: execute: execute-commands\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/a/b/c/my/topic.\n+\tremote: execute: post-receive hook\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/a/b/c/my/topic.\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"new execute-commands hook (fail with error)\" '\n+\tmv $bare/hooks/execute-commands $bare/hooks/execute-commands.ok &&\n+\tcat >$bare/hooks/execute-commands <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"execute: execute-commands\\n\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\tprintf >&2 \">> old: \\$old, new: \\$new, ref: \\$ref.\\n\"\n+\tdone\n+\n+\tprintf >&2 \"fail to run execute-commands\\n\"\n+\texit 1\n+\tEOF\n+\tchmod a+x $bare/hooks/execute-commands\n+'\n+\n+test_expect_success \"successfully push normal ref, and fail to push special reference\" '\n+\t(\n+\t\tcd work &&\n+\t\ttest_must_fail git push origin \\\n+\t\t\tHEAD:refs/for/maint/my/topic \\\n+\t\t\tHEAD:refs/heads/master\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: execute-commands--pre-receive\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: 102939797ab91a4f201d131418d2c9d919dcdd2c, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/heads/master.\n+\tremote: execute: execute-commands\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n+\tremote: fail to run execute-commands\n+\tremote: execute: post-receive hook\n+\tremote: >> old: 102939797ab91a4f201d131418d2c9d919dcdd2c, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/heads/master.\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"restore remote master branch\" '\n+\t(\n+\t\tcd $bare &&\n+\t\tgit update-ref refs/heads/master $A $B &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-eof &&\n+\t102939797ab91a4f201d131418d2c9d919dcdd2c refs/heads/a/b/c\n+\t102939797ab91a4f201d131418d2c9d919dcdd2c refs/heads/maint\n+\t102939797ab91a4f201d131418d2c9d919dcdd2c refs/heads/master\n+\teof\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"all mixed refs are failed to push in atomic mode\" '\n+\t(\n+\t\tcd work &&\n+\t\ttest_must_fail git push --atomic origin \\\n+\t\t\tHEAD:refs/for/maint/my/topic \\\n+\t\t\tHEAD:refs/heads/master\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: execute-commands--pre-receive\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: 102939797ab91a4f201d131418d2c9d919dcdd2c, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/heads/master.\n+\tremote: execute: execute-commands\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n+\tremote: fail to run execute-commands\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"restore execute-commands hook\" '\n+\tmv $bare/hooks/execute-commands $bare/hooks/execute-commands.fail &&\n+\tmv $bare/hooks/execute-commands.ok $bare/hooks/execute-commands\n+'\n+\n+test_expect_failure \"push mixed references successfully\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit push origin \\\n+\t\t\tHEAD:refs/for/maint/my/topic \\\n+\t\t\tHEAD:refs/heads/master\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: execute-commands--pre-receive\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: 102939797ab91a4f201d131418d2c9d919dcdd2c, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/heads/master.\n+\tremote: execute: execute-commands\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n+\tremote: execute: post-receive hook\n+\tremote: >> old: 102939797ab91a4f201d131418d2c9d919dcdd2c, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/heads/master.\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"restore remote master branch\" '\n+\t(\n+\t\tcd $bare &&\n+\t\tgit update-ref refs/heads/master $A $B &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t102939797ab91a4f201d131418d2c9d919dcdd2c refs/heads/a/b/c\n+\t102939797ab91a4f201d131418d2c9d919dcdd2c refs/heads/maint\n+\t102939797ab91a4f201d131418d2c9d919dcdd2c refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"new execute-commands--pre-receive hook (declined version)\" '\n+\tmv $bare/hooks/execute-commands--pre-receive $bare/hooks/execute-commands--pre-receive.ok &&\n+\tcat >$bare/hooks/execute-commands--pre-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"execute: execute-commands--pre-receive\\n\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\tprintf >&2 \">> old: \\$old, new: \\$new, ref: \\$ref.\\n\"\n+\tdone\n+\n+\tprintf >&2 \">> ERROR: declined in execute-commands--pre-receive\\n\"\n+\texit 1\n+\tEOF\n+\tchmod a+x $bare/hooks/execute-commands--pre-receive\n+'\n+\n+test_expect_success \"cannot push two special references (declined)\" '\n+\t(\n+\t\tcd work &&\n+\t\ttest_must_fail git push origin \\\n+\t\t\tHEAD:refs/for/master/my/topic \\\n+\t\t\tHEAD:refs/for/maint/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: execute-commands--pre-receive\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/master/my/topic.\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n+\tremote: >> ERROR: declined in execute-commands--pre-receive\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cannot push mixed references (declined)\" '\n+\t(\n+\t\tcd work &&\n+\t\ttest_must_fail git push origin \\\n+\t\t\tHEAD:refs/for/master/my/topic \\\n+\t\t\tHEAD:refs/heads/master\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: execute-commands--pre-receive\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/master/my/topic.\n+\tremote: >> ERROR: declined in execute-commands--pre-receive\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"new pre-receive hook (declined version)\" '\n+\tmv $bare/hooks/execute-commands--pre-receive $bare/hooks/execute-commands--pre-receive.fail &&\n+\tmv $bare/hooks/execute-commands--pre-receive.ok $bare/hooks/execute-commands--pre-receive &&\n+\tmv $bare/hooks/pre-receive $bare/hooks/pre-receive.ok &&\n+\tcat >$bare/hooks/pre-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"execute: pre-receive hook\\n\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\tprintf >&2 \">> old: \\$old, new: \\$new, ref: \\$ref.\\n\"\n+\tdone\n+\tprintf >&2 \">> ERROR: declined in pre-receive hook\\n\"\n+\texit 1\n+\tEOF\n+\tchmod a+x $bare/hooks/pre-receive\n+'\n+\n+test_expect_success \"cannot push mixed references (declined)\" '\n+\t(\n+\t\tcd work &&\n+\t\ttest_must_fail git push origin \\\n+\t\t\tHEAD:refs/for/master/my/topic \\\n+\t\t\tHEAD:refs/heads/master\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: execute-commands--pre-receive\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/master/my/topic.\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: 102939797ab91a4f201d131418d2c9d919dcdd2c, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/heads/master.\n+\tremote: >> ERROR: declined in pre-receive hook\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \n2.25.1.362.g51ebf55b93\n\n"},{"id":"392844","messageId":"20200304113312.34229-4-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH 3/7] receive-pack: try `execute-commands --pre-receive`","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-04T11:33:08Z","receivedAt":"2020-03-04T11:34:15Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"First we try to find the hook \"execute-command--pre-receive\" to check\npermissions for special commands.  If the hook does not exist, will try\nto find the hook \"execute-commands\" and run command `execute-commands\n--pre-receive` instead.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c           | 30 ++++++++++----\n t/t5411-execute-commands-hook.sh | 68 +++++++++++++++++++++++---------\n 2 files changed, 71 insertions(+), 27 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex c97abfbcd3..241b1d4cfc 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -683,20 +683,34 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n {\n \tstruct child_process proc = CHILD_PROCESS_INIT;\n \tstruct async muxer;\n-\tconst char *argv[2];\n+\tconst char *argv[3];\n \tint code;\n \n \targv[0] = find_hook(hook_name);\n \tif (!argv[0]) {\n-\t\tif (feed_state->hook_must_exist) {\n-\t\t\trp_error(\"cannot to find hook '%s'\", hook_name);\n-\t\t\treturn 1;\n-\t\t} else\n-\t\t\treturn 0;\n+\t\tchar *hook_helper;\n+\t\tchar *opt;\n+\n+\t\topt = strstr(hook_name, \"--\");\n+\t\tif (opt) {\n+\t\t\thook_helper = xstrdup(hook_name);\n+\t\t\thook_helper[opt - hook_name] = '\\0';\n+\t\t\targv[0] = find_hook(hook_helper);\n+\t\t\tfree(hook_helper);\n+\t\t}\n+\t\tif (!argv[0]) {\n+\t\t\tif (feed_state->hook_must_exist) {\n+\t\t\t\trp_error(\"cannot to find hook '%s'\", hook_name);\n+\t\t\t\treturn 1;\n+\t\t\t} else\n+\t\t\t\treturn 0;\n+\t\t}\n+\t\targv[1] = opt;\n+\t\targv[2] = NULL;\n+\t} else {\n+\t\targv[1] = NULL;\n \t}\n \n-\targv[1] = NULL;\n-\n \tproc.argv = argv;\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\ndiff --git a/t/t5411-execute-commands-hook.sh b/t/t5411-execute-commands-hook.sh\nindex b6444ca047..0bf14e702d 100755\n--- a/t/t5411-execute-commands-hook.sh\n+++ b/t/t5411-execute-commands-hook.sh\n@@ -64,6 +64,11 @@ test_expect_success \"setup hooks\" '\n \n \tprintf >&2 \"execute: execute-commands\\n\"\n \n+\tif test \\$# -gt 0 && test \"\\$1\" = \"--pre-receive\"\n+\tthen\n+\t\tprintf >&2 \">> pre-receive mode\\n\"\n+\tfi\n+\n \twhile read old new ref\n \tdo\n \t\tprintf >&2 \">> old: \\$old, new: \\$new, ref: \\$ref.\\n\"\n@@ -183,13 +188,15 @@ test_expect_success \"add back the execute-commands hook\" '\n '\n \n test_expect_success \"push one special ref: refs/for/a/b/c\" '\n+\tmv $bare/hooks/execute-commands--pre-receive $bare/hooks/execute-commands--pre-receive.ok &&\n \t(\n \t\tcd work &&\n \t\tgit push origin HEAD:refs/for/a/b/c/my/topic\n \t) >out 2>&1 &&\n \tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n \tcat >expect <<-EOF &&\n-\tremote: execute: execute-commands--pre-receive\n+\tremote: execute: execute-commands\n+\tremote: >> pre-receive mode\n \tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/a/b/c/my/topic.\n \tremote: execute: execute-commands\n \tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/a/b/c/my/topic.\n@@ -208,7 +215,8 @@ test_expect_success \"push two special references\" '\n \t) >out 2>&1 &&\n \tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n \tcat >expect <<-EOF &&\n-\tremote: execute: execute-commands--pre-receive\n+\tremote: execute: execute-commands\n+\tremote: >> pre-receive mode\n \tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n \tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/a/b/c/my/topic.\n \tremote: execute: execute-commands\n@@ -228,13 +236,21 @@ test_expect_success \"new execute-commands hook (fail with error)\" '\n \n \tprintf >&2 \"execute: execute-commands\\n\"\n \n+\tif test \\$# -gt 0 && test \"\\$1\" = \"--pre-receive\"\n+\tthen\n+\t\tprintf >&2 \">> pre-receive mode\\n\"\n+\tfi\n+\n \twhile read old new ref\n \tdo\n \t\tprintf >&2 \">> old: \\$old, new: \\$new, ref: \\$ref.\\n\"\n \tdone\n \n-\tprintf >&2 \"fail to run execute-commands\\n\"\n-\texit 1\n+\tif test \\$# -eq 0\n+\tthen\n+\t\tprintf >&2 \"fail to run execute-commands\\n\"\n+\t\texit 1\n+\tfi\n \tEOF\n \tchmod a+x $bare/hooks/execute-commands\n '\n@@ -248,7 +264,8 @@ test_expect_success \"successfully push normal ref, and fail to push special refe\n \t) >out 2>&1 &&\n \tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n \tcat >expect <<-EOF &&\n-\tremote: execute: execute-commands--pre-receive\n+\tremote: execute: execute-commands\n+\tremote: >> pre-receive mode\n \tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n \tremote: execute: pre-receive hook\n \tremote: >> old: 102939797ab91a4f201d131418d2c9d919dcdd2c, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/heads/master.\n@@ -284,7 +301,8 @@ test_expect_success \"all mixed refs are failed to push in atomic mode\" '\n \t) >out 2>&1 &&\n \tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n \tcat >expect <<-EOF &&\n-\tremote: execute: execute-commands--pre-receive\n+\tremote: execute: execute-commands\n+\tremote: >> pre-receive mode\n \tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n \tremote: execute: pre-receive hook\n \tremote: >> old: 102939797ab91a4f201d131418d2c9d919dcdd2c, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/heads/master.\n@@ -309,7 +327,8 @@ test_expect_success \"push mixed references successfully\" '\n \t) >out 2>&1 &&\n \tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n \tcat >expect <<-EOF &&\n-\tremote: execute: execute-commands--pre-receive\n+\tremote: execute: execute-commands\n+\tremote: >> pre-receive mode\n \tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n \tremote: execute: pre-receive hook\n \tremote: >> old: 102939797ab91a4f201d131418d2c9d919dcdd2c, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/heads/master.\n@@ -336,22 +355,30 @@ test_expect_success \"restore remote master branch\" '\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"new execute-commands--pre-receive hook (declined version)\" '\n-\tmv $bare/hooks/execute-commands--pre-receive $bare/hooks/execute-commands--pre-receive.ok &&\n-\tcat >$bare/hooks/execute-commands--pre-receive <<-EOF &&\n+test_expect_success \"new execute-commands hook (pre-receive declined)\" '\n+\tmv $bare/hooks/execute-commands $bare/hooks/execute-commands.ok &&\n+\tcat >$bare/hooks/execute-commands <<-EOF &&\n \t#!/bin/sh\n \n-\tprintf >&2 \"execute: execute-commands--pre-receive\\n\"\n+\tprintf >&2 \"execute: execute-commands\\n\"\n+\n+\tif test \\$# -gt 0 && test \"\\$1\" = \"--pre-receive\"\n+\tthen\n+\t\tprintf >&2 \">> pre-receive mode\\n\"\n+\tfi\n \n \twhile read old new ref\n \tdo\n \t\tprintf >&2 \">> old: \\$old, new: \\$new, ref: \\$ref.\\n\"\n \tdone\n \n-\tprintf >&2 \">> ERROR: declined in execute-commands--pre-receive\\n\"\n-\texit 1\n+\tif test \\$# -gt 0 && test \"\\$1\" = \"--pre-receive\"\n+\tthen\n+\t\tprintf >&2 \">> ERROR: declined in execute-commands--pre-receive\\n\"\n+\t\texit 1\n+\tfi\n \tEOF\n-\tchmod a+x $bare/hooks/execute-commands--pre-receive\n+\tchmod a+x $bare/hooks/execute-commands\n '\n \n test_expect_success \"cannot push two special references (declined)\" '\n@@ -363,7 +390,8 @@ test_expect_success \"cannot push two special references (declined)\" '\n \t) >out 2>&1 &&\n \tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n \tcat >expect <<-EOF &&\n-\tremote: execute: execute-commands--pre-receive\n+\tremote: execute: execute-commands\n+\tremote: >> pre-receive mode\n \tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/master/my/topic.\n \tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n \tremote: >> ERROR: declined in execute-commands--pre-receive\n@@ -380,7 +408,8 @@ test_expect_success \"cannot push mixed references (declined)\" '\n \t) >out 2>&1 &&\n \tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n \tcat >expect <<-EOF &&\n-\tremote: execute: execute-commands--pre-receive\n+\tremote: execute: execute-commands\n+\tremote: >> pre-receive mode\n \tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/master/my/topic.\n \tremote: >> ERROR: declined in execute-commands--pre-receive\n \tEOF\n@@ -388,8 +417,8 @@ test_expect_success \"cannot push mixed references (declined)\" '\n '\n \n test_expect_success \"new pre-receive hook (declined version)\" '\n-\tmv $bare/hooks/execute-commands--pre-receive $bare/hooks/execute-commands--pre-receive.fail &&\n-\tmv $bare/hooks/execute-commands--pre-receive.ok $bare/hooks/execute-commands--pre-receive &&\n+\tmv $bare/hooks/execute-commands $bare/hooks/execute-commands.fail &&\n+\tmv $bare/hooks/execute-commands.ok $bare/hooks/execute-commands &&\n \tmv $bare/hooks/pre-receive $bare/hooks/pre-receive.ok &&\n \tcat >$bare/hooks/pre-receive <<-EOF &&\n \t#!/bin/sh\n@@ -415,7 +444,8 @@ test_expect_success \"cannot push mixed references (declined)\" '\n \t) >out 2>&1 &&\n \tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n \tcat >expect <<-EOF &&\n-\tremote: execute: execute-commands--pre-receive\n+\tremote: execute: execute-commands\n+\tremote: >> pre-receive mode\n \tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/master/my/topic.\n \tremote: execute: pre-receive hook\n \tremote: >> old: 102939797ab91a4f201d131418d2c9d919dcdd2c, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/heads/master.\n-- \n2.25.1.362.g51ebf55b93\n\n"},{"id":"392845","messageId":"20200304113312.34229-5-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH 4/7] receive-pack: read env from execute-commands output","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-04T11:33:09Z","receivedAt":"2020-03-04T11:34:17Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"The “post-receive” hook may need the pull request ID generated by the\n“execute-commands” hook.  The results can be passed between hooks by\nenvironment variables.\n\nEach line of the message received from the standard output of the\n“execute-commands” in the key=value format is parsed as environment and\nthese variables will be sent to environment of the “post-receive” hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c           | 42 +++++++++++++++--\n t/t5411-execute-commands-hook.sh | 79 ++++++++++++++++++++++++++++++++\n 2 files changed, 117 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 241b1d4cfc..fd2f3ba80a 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -85,6 +85,7 @@ static const char *nonce_status;\n static long nonce_stamp_slop;\n static timestamp_t nonce_stamp_slop_limit;\n static struct ref_transaction *transaction;\n+struct argv_array post_receive_env_array;\n \n static enum {\n \tKEEPALIVE_NEVER = 0,\n@@ -678,7 +679,9 @@ struct receive_hook_feed_state {\n };\n \n typedef int (*feed_fn)(void *, const char **, size_t *);\n+typedef void (*stdout_handler_fn)(int out);\n static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n+\t\t\t     stdout_handler_fn stdout_handler,\n \t\t\t     struct receive_hook_feed_state *feed_state)\n {\n \tstruct child_process proc = CHILD_PROCESS_INIT;\n@@ -713,9 +716,15 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n \n \tproc.argv = argv;\n \tproc.in = -1;\n-\tproc.stdout_to_stderr = 1;\n+\tif (stdout_handler)\n+\t\tproc.out = -1;\n+\telse\n+\t\tproc.stdout_to_stderr = 1;\n \tproc.trace2_hook_name = hook_name;\n \n+\tif (!strcmp(hook_name, \"post-receive\") && post_receive_env_array.argc > 0)\n+\t\targv_array_pushv(&proc.env_array, post_receive_env_array.argv);\n+\n \tif (feed_state->push_options) {\n \t\tint i;\n \t\tfor (i = 0; i < feed_state->push_options->nr; i++)\n@@ -760,6 +769,10 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n \t\t\tbreak;\n \t}\n \tclose(proc.in);\n+\n+\tif (stdout_handler)\n+\t\tstdout_handler(proc.out);\n+\n \tif (use_sideband)\n \t\tfinish_async(&muxer);\n \n@@ -817,7 +830,7 @@ static int run_receive_hook(struct command *commands,\n \t\treturn 0;\n \tstate.cmd = commands;\n \tstate.push_options = push_options;\n-\tstatus = run_and_feed_hook(hook_name, feed_receive_hook, &state);\n+\tstatus = run_and_feed_hook(hook_name, feed_receive_hook, NULL, &state);\n \tstrbuf_release(&state.buf);\n \treturn status;\n }\n@@ -868,11 +881,29 @@ static int run_execute_commands_pre_receive_hook(struct command *commands,\n \tstate.cmd = commands;\n \tstate.push_options = push_options;\n \tstatus = run_and_feed_hook(\"execute-commands--pre-receive\",\n-\t\t\tfeed_receive_hook, &state);\n+\t\t\tfeed_receive_hook, NULL, &state);\n \tstrbuf_release(&state.buf);\n \treturn status;\n }\n \n+\n+static void prepare_post_receive_env(int in)\n+{\n+\tstruct strbuf stdout_buf = STRBUF_INIT;\n+\n+\twhile (strbuf_getwholeline_fd(&stdout_buf, in, '\\n') != EOF) {\n+\t\tchar *p = stdout_buf.buf + stdout_buf.len -1;\n+\t\tif (*p =='\\n')\n+\t\t\t*p = '\\0';\n+\t\tp = strchr(stdout_buf.buf, '=');\n+\t\tif (p == NULL)\n+\t\t\tcontinue;\n+\t\targv_array_push(&post_receive_env_array, stdout_buf.buf);\n+\t\tstrbuf_reset(&stdout_buf);\n+\t}\n+\tstrbuf_release(&stdout_buf);\n+}\n+\n static int run_execute_commands_hook(struct command *commands,\n \t\t\t\t     const struct string_list *push_options)\n {\n@@ -889,7 +920,8 @@ static int run_execute_commands_hook(struct command *commands,\n \t\treturn 0;\n \tstate.cmd = commands;\n \tstate.push_options = push_options;\n-\tstatus = run_and_feed_hook(\"execute-commands\", feed_receive_hook, &state);\n+\tstatus = run_and_feed_hook(\"execute-commands\",\n+\t\t\tfeed_receive_hook, prepare_post_receive_env, &state);\n \tstrbuf_release(&state.buf);\n \treturn status;\n }\n@@ -2052,6 +2084,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n+\targv_array_init(&post_receive_env_array);\n+\n \tpacket_trace_identity(\"receive-pack\");\n \n \targc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);\ndiff --git a/t/t5411-execute-commands-hook.sh b/t/t5411-execute-commands-hook.sh\nindex 0bf14e702d..1907d0619d 100755\n--- a/t/t5411-execute-commands-hook.sh\n+++ b/t/t5411-execute-commands-hook.sh\n@@ -454,4 +454,83 @@ test_expect_success \"cannot push mixed references (declined)\" '\n \ttest_cmp expect actual\n '\n \n+test_expect_success \"new execute-commands and post-receive hooks (environments in output)\" '\n+\t## execute-commands hook\n+\tmv $bare/hooks/execute-commands $bare/hooks/execute-commands.ok &&\n+\tcat >$bare/hooks/execute-commands <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"execute: execute-commands\\n\"\n+\n+\tif test \\$# -gt 0 && test \"\\$1\" = \"--pre-receive\"\n+\tthen\n+\t\tprintf >&2 \">> pre-receive mode\\n\"\n+\telse\n+\t\tprintf \"GIT_VAR1=var1\\n\"\n+\t\tprintf \"GIT_VAR2=var2\\n\"\n+\t\tprintf \"AGIT_VAR1=foo\\n\"\n+\t\tprintf \"AGIT_VAR2=bar\\n\"\n+\tfi\n+\n+\twhile read old new ref\n+\tdo\n+\t\tprintf >&2 \">> old: \\$old, new: \\$new, ref: \\$ref.\\n\"\n+\tdone\n+\n+\tfor k in GIT_VAR1 GIT_VAR2 AGIT_VAR1 AGIT_VAR2\n+\tdo\n+\t\tif test -n \"\\$(eval echo \\\\\"\\\\\\$\\$k\\\")\"\n+\t\tthen\n+\t\t\tprintf >&2 \">> has env: \\$k=\\$(eval echo \\\\\"\\\\\\$\\$k\\\").\\n\"\n+\t\tfi\n+\tdone\n+\tEOF\n+\tchmod a+x $bare/hooks/execute-commands &&\n+\n+\t## post-receive hook\n+\tmv $bare/hooks/post-receive $bare/hooks/post-receive.ok &&\n+\tcat >$bare/hooks/post-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"execute: post-receive hook\\n\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\tprintf >&2 \">> old: \\$old, new: \\$new, ref: \\$ref.\\n\"\n+\tdone\n+\n+\tfor k in GIT_VAR1 GIT_VAR2 AGIT_VAR1 AGIT_VAR2\n+\tdo\n+\t\tif test -n \"\\$(eval echo \\\\\"\\\\\\$\\$k\\\")\"\n+\t\tthen\n+\t\t\tprintf >&2 \">> has env: \\$k=\\$(eval echo \\\\\"\\\\\\$\\$k\\\").\\n\"\n+\t\tfi\n+\tdone\n+\tEOF\n+\tchmod a+x $bare/hooks/post-receive\n+'\n+\n+test_expect_success \"push and show environments\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit push origin \\\n+\t\t\tHEAD:refs/for/master/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: execute-commands\n+\tremote: >> pre-receive mode\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/master/my/topic.\n+\tremote: execute: execute-commands\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/master/my/topic.\n+\tremote: execute: post-receive hook\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/master/my/topic.\n+\tremote: >> has env: GIT_VAR1=var1.\n+\tremote: >> has env: GIT_VAR2=var2.\n+\tremote: >> has env: AGIT_VAR1=foo.\n+\tremote: >> has env: AGIT_VAR2=bar.\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.25.1.362.g51ebf55b93\n\n"},{"id":"392846","messageId":"20200304113312.34229-6-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH 5/7] refs.c: refactor to reuse ref_is_hidden()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-04T11:33:10Z","receivedAt":"2020-03-04T11:34:18Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Add new function `ref_is_matched()` to reuse `ref_is_hidden()`. Test\ncase t5512 covered this change.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n refs.c | 11 ++++++++---\n refs.h |  1 +\n 2 files changed, 9 insertions(+), 3 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 1ab0bb54d3..229159ea1a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1389,13 +1389,18 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti\n }\n \n int ref_is_hidden(const char *refname, const char *refname_full)\n+{\n+\treturn ref_is_matched(hide_refs, refname, refname_full);\n+}\n+\n+int ref_is_matched(struct string_list *match_refs, const char *refname, const char *refname_full)\n {\n \tint i;\n \n-\tif (!hide_refs)\n+\tif (!match_refs)\n \t\treturn 0;\n-\tfor (i = hide_refs->nr - 1; i >= 0; i--) {\n-\t\tconst char *match = hide_refs->items[i].string;\n+\tfor (i = match_refs->nr - 1; i >= 0; i--) {\n+\t\tconst char *match = match_refs->items[i].string;\n \t\tconst char *subject;\n \t\tint neg = 0;\n \t\tconst char *p;\ndiff --git a/refs.h b/refs.h\nindex 545029c6d8..a2ea043f7f 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -739,6 +739,7 @@ int parse_hide_refs_config(const char *var, const char *value, const char *);\n  * parameter always points to the full ref name.\n  */\n int ref_is_hidden(const char *, const char *);\n+int ref_is_matched(struct string_list *, const char *, const char *);\n \n enum ref_type {\n \tREF_TYPE_PER_WORKTREE,\t  /* refs inside refs/ but not shared       */\n-- \n2.25.1.362.g51ebf55b93\n\n"},{"id":"392847","messageId":"20200304113312.34229-7-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH 6/7] receive-pack: new config receive.executeCommandsHookRefs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-04T11:33:11Z","receivedAt":"2020-03-04T11:34:20Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Add a new multi-valued config variable \"receive.executeCommandsHookRefs\"\nfor `receive-pack` command, like the follows:\n\n    git config --system --add receive.executeCommandsHookRefs refs/for/\n    git config --system --add receive.executeCommandsHookRefs refs/drafts/\n\nIf the specific prefix strings match the reference names of the commands\nwhich are sent by git client to `receive-pack`, these commands will be\nexecuted by an external hook (named \"execute-commands\"), instead of the\ninternal `execute_commands` function.\n\nFor example, if it is set to \"refs/for/\", pushing to a reference such as\n\"refs/for/master\" will not create or update reference \"refs/for/master\",\nbut may create or update a pull request directly by running the external\nhook.  Use more than one definition to specify multiple prefix strings.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/config/receive.txt | 11 +++++\n builtin/receive-pack.c           | 48 ++++++++++++++++-----\n t/t5411-execute-commands-hook.sh | 72 ++++++++++++++++++++++++++++++--\n 3 files changed, 117 insertions(+), 14 deletions(-)\n\ndiff --git a/Documentation/config/receive.txt b/Documentation/config/receive.txt\nindex 65f78aac37..a8e2125e62 100644\n--- a/Documentation/config/receive.txt\n+++ b/Documentation/config/receive.txt\n@@ -108,6 +108,17 @@ receive.denyNonFastForwards::\n \teven if that push is forced. This configuration variable is\n \tset when initializing a shared repository.\n \n+receive.executeCommandsHookRefs::\n+\tIf the specific prefix matches the reference name of the command\n+\twhich is sent by the client to `receive-pack`, the command will be\n+\texecuted by an external hook (named \"execute-commands\"), instead of\n+\tthe internal `execute_commands` function. For example, if it is\n+\tset to \"refs/for/\", pushing to reference such as \"refs/for/master\"\n+\twill not create or update a reference named \"refs/for/master\", but\n+\tmay create or update a pull request directly by running the external\n+\thook. Use more than one definition to specify multiple prefix\n+\tstrings.\n+\n receive.hideRefs::\n \tThis variable is the same as `transfer.hideRefs`, but applies\n \tonly to `receive-pack` (and so affects pushes, but not fetches).\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex fd2f3ba80a..f78adeffd4 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -75,6 +75,7 @@ static struct object_id push_cert_oid;\n static struct signature_check sigcheck;\n static const char *push_cert_nonce;\n static const char *cert_nonce_seed;\n+static struct string_list execute_commands_hook_refs;\n \n static const char *NONCE_UNSOLICITED = \"UNSOLICITED\";\n static const char *NONCE_BAD = \"BAD\";\n@@ -228,6 +229,20 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (strcmp(var, \"receive.executecommandshookrefs\") == 0) {\n+\t\tchar *prefix;\n+\t\tint len;\n+\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tprefix = xstrdup(value);\n+\t\tlen = strlen(prefix);\n+\t\twhile (len && prefix[len - 1] == '/')\n+\t\t\tprefix[--len] = '\\0';\n+\t\tstring_list_insert(&execute_commands_hook_refs, prefix);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -1600,17 +1615,29 @@ static void execute_commands(struct command *commands,\n \t/* Try to find commands that have special prefix, and will run these\n \t * commands using an external \"execute-commands\" hook.\n \t */\n-\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n-\t\t\tcontinue;\n+\tif (execute_commands_hook_refs.nr > 0) {\n+\t\tstruct strbuf refname_full = STRBUF_INIT;\n+\t\tsize_t prefix_len;\n \n-\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n-\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n-\t\t\tcmd->exec_by_hook = 1;\n-\t\t\tseen_exec_by_hook = 1;\n-\t\t} else\n-\t\t\tseen_internal_exec = 1;\n-\t}\n+\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n+\t\tprefix_len = refname_full.len;\n+\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tif (!should_process_cmd(cmd))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_setlen(&refname_full, prefix_len);\n+\t\t\tstrbuf_addstr(&refname_full, cmd->ref_name);\n+\t\t\tif (ref_is_matched(&execute_commands_hook_refs, cmd->ref_name, refname_full.buf)) {\n+\t\t\t\tcmd->exec_by_hook = 1;\n+\t\t\t\tseen_exec_by_hook = 1;\n+\t\t\t} else\n+\t\t\t\tseen_internal_exec = 1;\n+\t\t}\n+\n+\t\tstrbuf_release(&refname_full);\n+\t} else\n+\t\tseen_internal_exec = 1;\n \n \tif (seen_exec_by_hook) {\n \t\t/* Try to find and run the `execute-commands--pre-receive` hook to check\n@@ -2085,6 +2112,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t};\n \n \targv_array_init(&post_receive_env_array);\n+\tstring_list_init(&execute_commands_hook_refs, 0);\n \n \tpacket_trace_identity(\"receive-pack\");\n \ndiff --git a/t/t5411-execute-commands-hook.sh b/t/t5411-execute-commands-hook.sh\nindex 1907d0619d..c8ee699773 100755\n--- a/t/t5411-execute-commands-hook.sh\n+++ b/t/t5411-execute-commands-hook.sh\n@@ -41,6 +41,9 @@ test_expect_success setup '\n \t# Enable push options for bare.git.\n \tgit -C $bare config receive.advertisePushOptions true &&\n \n+\t# Register ref prefix for execute-commands hook.\n+\tgit -C $bare config --add receive.executeCommandsHookRefs refs/for/ &&\n+\n \tgit clone --no-local $bare work &&\n \tcreate_commits_in work A B\n '\n@@ -229,6 +232,67 @@ test_expect_success \"push two special references\" '\n \ttest_cmp expect actual\n '\n \n+test_expect_success \"push two special references, but one is not registered\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit push origin \\\n+\t\t\tHEAD:refs/for/maint/my/topic \\\n+\t\t\tHEAD:refs/drafts/maint/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: execute-commands\n+\tremote: >> pre-receive mode\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/drafts/maint/my/topic.\n+\tremote: execute: execute-commands\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n+\tremote: execute: post-receive hook\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/drafts/maint/my/topic.\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"restore bare.git, and register new ref prefix\" '\n+\t(\n+\t\tcd $bare &&\n+\t\tgit config --add receive.executeCommandsHookRefs refs/drafts/ &&\n+\t\tgit update-ref -d refs/drafts/maint/my/topic &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t102939797ab91a4f201d131418d2c9d919dcdd2c refs/heads/a/b/c\n+\t102939797ab91a4f201d131418d2c9d919dcdd2c refs/heads/maint\n+\t102939797ab91a4f201d131418d2c9d919dcdd2c refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"push to two special references (all registered)\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit push origin \\\n+\t\t\tHEAD:refs/for/master/my/topic \\\n+\t\t\tHEAD:refs/drafts/maint/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: execute-commands\n+\tremote: >> pre-receive mode\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/master/my/topic.\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/drafts/maint/my/topic.\n+\tremote: execute: execute-commands\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/master/my/topic.\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/drafts/maint/my/topic.\n+\tremote: execute: post-receive hook\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/master/my/topic.\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/drafts/maint/my/topic.\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success \"new execute-commands hook (fail with error)\" '\n \tmv $bare/hooks/execute-commands $bare/hooks/execute-commands.ok &&\n \tcat >$bare/hooks/execute-commands <<-EOF &&\n@@ -322,21 +386,21 @@ test_expect_success \"push mixed references successfully\" '\n \t(\n \t\tcd work &&\n \t\tgit push origin \\\n-\t\t\tHEAD:refs/for/maint/my/topic \\\n+\t\t\tHEAD:refs/drafts/maint/my/topic \\\n \t\t\tHEAD:refs/heads/master\n \t) >out 2>&1 &&\n \tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n \tcat >expect <<-EOF &&\n \tremote: execute: execute-commands\n \tremote: >> pre-receive mode\n-\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/drafts/maint/my/topic.\n \tremote: execute: pre-receive hook\n \tremote: >> old: 102939797ab91a4f201d131418d2c9d919dcdd2c, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/heads/master.\n \tremote: execute: execute-commands\n-\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/drafts/maint/my/topic.\n \tremote: execute: post-receive hook\n \tremote: >> old: 102939797ab91a4f201d131418d2c9d919dcdd2c, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/heads/master.\n-\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/maint/my/topic.\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/drafts/maint/my/topic.\n \tEOF\n \ttest_cmp expect actual\n '\n-- \n2.25.1.362.g51ebf55b93\n\n"},{"id":"392848","messageId":"20200304113312.34229-8-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH 7/7] hook: add document and example for \"execute-commands\" hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-04T11:33:12Z","receivedAt":"2020-03-04T11:34:23Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/githooks.txt               |  43 ++++++++\n t/t5411-execute-commands-hook.sh         |  98 +++++++++++++++++\n templates/hooks--execute-commands.sample | 131 +++++++++++++++++++++++\n 3 files changed, 272 insertions(+)\n create mode 100755 templates/hooks--execute-commands.sample\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 3dccab5375..6c21ab6db2 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -58,6 +58,49 @@ the message file.\n The default 'applypatch-msg' hook, when enabled, runs the\n 'commit-msg' hook, if the latter is enabled.\n \n+execute-commands--pre-receive\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+This hook is invoked by linkgit:git-receive-pack[1] when it reacts to\n+special `git push` command.  Just before starting to execute the\n+external 'execute-commands' hook to make changes to the repository,\n+the 'execute-commands--pre-receive' hook is invoked.  This hook has\n+the same functionality as 'pre-receive' hook, while it only acts on\n+special commands which 'git-push' sends to 'git-receive-pack'.\n+The refnames of these spacial commands have special prefix (such as\n+\"refs/for/\") which matches what the `receive.executeCommandsHookRefs`\n+configuration variable defines.\n+\n+If this hook does not exist, will try to find the 'execute-commands'\n+hook, and run `execute-command --pre-receive` command to do some\n+checks.  Its exit status determines the success or failure of the\n+update.  If the hook exits with non-zero status, won't execute any of\n+the commands, no metter calling the internal `execute_commands`\n+function or calling the external \"execute-commands\" hook.\n+\n+This hook executes once for the receive operation, and gets the\n+information from its standard input.\n+See <<pre-receive,'pre-receive'>> for details.\n+\n+execute-commands\n+~~~~~~~~~~~~~~~~\n+This hook is invoked by linkgit:git-receive-pack[1] when it reacts to\n+special `git push` command.  According to refnames of the commands which\n+`git push` sends to 'git-receive-pack', the commands will be devided\n+into two groups by matching what the `receive.executeCommandsHookRefs`\n+configuration variable defines.  One group of the commands will execute\n+the internal `execute_commands` function to update the corresponding\n+refnames, and the other group of commands which have matching refnames\n+will execute this external 'execute-commands' hook to create pull\n+requests, etc.\n+\n+Its exit status only determines the success or failure of the group of\n+commands with special refnames, unless atomic push is in use.\n+\n+This hook executes once if there are any special commands with special\n+refnames.  There is no argument taken by this hook, and the push options\n+(if any) and command(s) will be fed to this book by its standard input.\n+See the <<pre-receive,'pre-receive'>> hook for the format of the input.\n+\n pre-applypatch\n ~~~~~~~~~~~~~~\n \ndiff --git a/t/t5411-execute-commands-hook.sh b/t/t5411-execute-commands-hook.sh\nindex c8ee699773..6dd1560f9d 100755\n--- a/t/t5411-execute-commands-hook.sh\n+++ b/t/t5411-execute-commands-hook.sh\n@@ -597,4 +597,102 @@ test_expect_success \"push and show environments\" '\n \ttest_cmp expect actual\n '\n \n+test_expect_success \"execute-commands.sample: new execute-commands hook from templates/execute-commands.sample\" '\n+\tmv $bare/hooks/pre-receive $bare/hooks/pre-receive.fail &&\n+\tmv $bare/hooks/pre-receive.ok $bare/hooks/pre-receive &&\n+\tmv $bare/hooks/post-receive $bare/hooks/post-receive.env &&\n+\tmv $bare/hooks/post-receive.ok $bare/hooks/post-receive &&\n+\tmv $bare/hooks/execute-commands $bare/hooks/execute-commands.env &&\n+\tcp ../../templates/hooks--execute-commands.sample $bare/hooks/execute-commands &&\n+\tchmod a+x $bare/hooks/execute-commands\n+'\n+\n+test_expect_success \"execute-commands.sample: show push result\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit push origin \\\n+\t\t\tHEAD:refs/for/a/b/c/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: 102939797ab91a4f201d131418d2c9d919dcdd2c\n+\tremote: [execute-commands] *******************************************************\n+\tremote: [execute-commands] * Pull request #12345678901 created/updated           *\n+\tremote: [execute-commands] * URL: https://... ...                                *\n+\tremote: [execute-commands] *******************************************************\n+\tremote: execute: post-receive hook\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/a/b/c/my/topic.\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"execute-commands.sample: show debug info\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit push -o debug=1 -o reviewers=user1,user2 \\\n+\t\t\torigin \\\n+\t\t\tHEAD:refs/for/a/b/c/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: [DEBUG] [execute-commands] push-option: AGIT_DEBUG=1\n+\tremote: [DEBUG] [execute-commands] push-option: AGIT_REVIEWERS=user1,user2\n+\tremote: [DEBUG] [execute-commands] command from stdin: 0000000000000000000000000000000000000000 ce858e653cdbf70f9955a39d73a44219e4b92e9e refs/for/a/b/c/my/topic\n+\tremote: 102939797ab91a4f201d131418d2c9d919dcdd2c\n+\tremote: [DEBUG] [execute-commands: pre-receive] check permissions...\n+\tremote: [DEBUG] [execute-commands] push-option: AGIT_DEBUG=1\n+\tremote: [DEBUG] [execute-commands] push-option: AGIT_REVIEWERS=user1,user2\n+\tremote: [DEBUG] [execute-commands] command from stdin: 0000000000000000000000000000000000000000 ce858e653cdbf70f9955a39d73a44219e4b92e9e refs/for/a/b/c/my/topic\n+\tremote: [DEBUG] [execute-commands] call API (AGIT_PR_TARGET=a/b/c, AGIT_PR_TOPIC=)...\n+\tremote: [DEBUG] [execute-commands] parse API result, and get AGIT_PR_ID, etc.\n+\tremote: [execute-commands] *******************************************************\n+\tremote: [execute-commands] * Pull request #12345678901 created/updated           *\n+\tremote: [execute-commands] * URL: https://... ...                                *\n+\tremote: [execute-commands] *******************************************************\n+\tremote: [DEBUG] [execute-commands] output kv pairs to stdout for git to parse.\n+\tremote: execute: post-receive hook\n+\tremote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/a/b/c/my/topic.\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"execute-commands.sample: fail to push refs/for/maint\" '\n+\t(\n+\t\tcd work &&\n+\t\ttest_must_fail git push -o reviewers=user1,user2 \\\n+\t\t\torigin \\\n+\t\t\tHEAD:refs/for/maint/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: 102939797ab91a4f201d131418d2c9d919dcdd2c\n+\tremote: [execute-commands: pre-receive] send pull request to maint branch is not allowed\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"execute-commands.sample: fail to push non-exist branch\" '\n+\t(\n+\t\tcd work &&\n+\t\ttest_must_fail git push -o reviewers=user1,user2 \\\n+\t\t\torigin \\\n+\t\t\tHEAD:refs/for/a/b/x/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: [execute-commands] cannot find target branch from ref: refs/for/a/b/x/my/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"show refs of the repository using git-show-ref\" '\n+\tgit -C $bare show-ref >actual &&\n+\tcat >expect <<-EOF &&\n+\t102939797ab91a4f201d131418d2c9d919dcdd2c refs/heads/a/b/c\n+\t102939797ab91a4f201d131418d2c9d919dcdd2c refs/heads/maint\n+\t102939797ab91a4f201d131418d2c9d919dcdd2c refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\ndiff --git a/templates/hooks--execute-commands.sample b/templates/hooks--execute-commands.sample\nnew file mode 100755\nindex 0000000000..d061984bca\n--- /dev/null\n+++ b/templates/hooks--execute-commands.sample\n@@ -0,0 +1,131 @@\n+#!/bin/sh\n+#\n+# This is an  example hook script, DO NOT use it on production service.\n+\n+debug() {\n+\tcase \"$AGIT_DEBUG\" in\n+\t\"yes\" | \"true\" | \"1\")\n+\t\t;;\n+\t*)\n+\t\treturn\n+\tesac\n+\n+\techo >&2 \"[DEBUG] $@\"\n+}\n+\n+# Parse push options\n+if test -n \"$GIT_PUSH_OPTION_COUNT\"\n+then\n+\ti=0\n+\twhile test \"$i\" -lt \"$GIT_PUSH_OPTION_COUNT\"\n+\tdo\n+\t\teval \"value=\\$GIT_PUSH_OPTION_$i\"\n+\t\ti=$((i + 1))\n+\n+\t\tk=$(echo ${value%=*} | tr [a-z] [A-Z])\n+\t\tv=${value#*=}\n+\t\tif test -n \"$v\" && test -n \"$k\"\n+\t\tthen\n+\t\t\tk=\"AGIT_$k\"\n+\t\telse\n+\t\t\tcontinue\n+\t\tfi\n+\t\teval \"$k=$v\"\n+\t\tdebug \"[execute-commands] push-option: $k=$v\"\n+\tdone\n+fi\n+\n+# Read push commands.\n+count=0\n+while read old new refname\n+do\n+\tdebug \"[execute-commands] command from stdin: $old $new $refname\"\n+\tcount=$(( count + 1 ))\n+\t# Only one special refname is allowed for each push\n+\tif test $count -gt 1\n+\tthen\n+\t\techo >&2 \"[execute-commands]: cannot handle more than one push commands\"\n+\t\texit 1\n+\tfi\n+\n+\t# Parse refname, and set envrionment\n+\tremains=\n+\tif test \"${refname#refs/for/}\" != \"$refname\"\n+\tthen\n+\t\tAGIT_PR_IS_DRAFT=false\n+\t\tremains=${refname#refs/for/}\n+\telif test \"${refname#refs/drafts/}\" != \"$refname\"\n+\tthen\n+\t\tAGIT_PR_IS_DRAFT=true\n+\t\tremains=${refname#refs/drafts/}\n+\telse\n+\t\techo >&2 \"[execute-commands] unknown refname: $refname\"\n+\t\texit 1\n+\tfi\n+\n+\tref=\n+\tfound_ref=\n+\tfor i in $(echo $remains | tr \"/\" \"\\n\")\n+\tdo\n+\t\tif test -z \"$ref\"\n+\t\tthen\n+\t\t\tref=$i\n+\t\telse\n+\t\t\tref=$ref/$i\n+\t\tfi\n+\t\tif git rev-parse --verify $ref -- 2>/dev/null\n+\t\tthen\n+\t\t\tfound_ref=yes\n+\t\t\tbreak\n+\t\tfi\n+\tdone\n+\tif test -z \"$found_ref\"\n+\tthen\n+\t\techo >&2 \"[execute-commands] cannot find target branch from ref: $refname\"\n+\t\texit 1\n+\tfi\n+\tAGIT_PR_TARGET=$ref\n+\tAGIT_PR_SOURCE=${remains#$ref/}\n+done\n+\n+if test -z \"$AGIT_PR_TARGET\"\n+then\n+\techo >&2 \"[execute-commands] fail to parse refname, no target found\"\n+\texit 1\n+fi\n+\n+# pre-receive mode, used to check permissions.\n+if test \"$1\" = \"--pre-receive\"\n+then\n+\tdebug \"[execute-commands: pre-receive] check permissions...\"\n+\tif test \"$AGIT_PR_TARGET\" = \"maint\"\n+\tthen\n+\t\techo >&2 \"[execute-commands: pre-receive] send pull request to maint branch is not allowed\"\n+\t\texit 1\n+\tfi\n+\texit 0\n+fi\n+\n+# Call API to generate code review.\n+debug \"[execute-commands] call API (AGIT_PR_TARGET=$AGIT_PR_TARGET, AGIT_PR_TOPIC=$AGIT_PR_TOPIC)...\"\n+\n+# Parse result of API.\n+debug \"[execute-commands] parse API result, and get AGIT_PR_ID, etc.\"\n+AGIT_PR_ID=\"12345678901\"\n+AGIT_PR_LOCAL_ID=\"23\"\n+\n+# Show message.\n+if test -n \"$AGIT_PR_ID\"\n+then\n+\techo >&2 \"[execute-commands] *******************************************************\"\n+\techo >&2 \"[execute-commands] * Pull request #$AGIT_PR_ID created/updated           *\"\n+\techo >&2 \"[execute-commands] * URL: https://... ...                                *\"\n+\techo >&2 \"[execute-commands] *******************************************************\"\n+fi\n+\n+# Show envs to stdout, and will be exported as envs for \"post-receive\" hook.\n+debug \"[execute-commands] output kv pairs to stdout for git to parse.\"\n+echo \"AGIT_PR_ID=$AGIT_PR_ID\"\n+echo \"AGIT_PR_LOCAL_ID=$AGIT_PR_LOCAL_ID\"\n+\n+exit 0\n-- \n2.25.1.362.g51ebf55b93\n\n"},{"id":"392871","messageId":"xmqqv9nj4zxk.fsf@gitster-ct.c.googlers.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"Re: [PATCH 0/7] New execute-commands hook for centralized workflow","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-03-04T20:39:51Z","receivedAt":"2020-03-04T20:39:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> It would be more convenient to work in a centralized workflow like what\n> Gerrit provided for some cases.  For example, a read-only user may run\n> the following `git push` command to push commits to a special reference\n> to create a code review, instead of updating a reference directly.\n>\n>     git push -o reviewers=user1,user2 \\\n>         -o oldoid=89c082363ac950d224a7259bfba3ccfbf4c560c4 \\\n>         origin \\\n>         HEAD:refs/for/<branch-name>/<session>\n>\n> The `<branch-name>` in the above example can be as simple as \"master\",\n> or a more complicated branch name like \"foo/bar\".  The `<session>` in\n> the above example command can be the local branch name of the clien-\n> side, such as \"my/topic\".\n>\n> To support this kind of workflow in CGit, add a filter and a new\n> handler.  The filter will check the prefix of the reference name, and\n> if the command has a special reference name, the filter will add a\n> specific tag (`exec_by_hook`) to the command.  Commands with this\n> specific tag will be executed by a new handler (an external hook named\n> \"execute-commands\") instead of the internal `execute_commands` function.\n\nI do not claim to be great at naming, but you are worse ;-)\n\n - Any hook is about executing command(s), so \"execute-commands\"\n   hook does not give any information to users.\n\n - IIUC, this is only about what happens when accepting a push and\n   is not called at any other time.  Naming your hook without\n   \"receive\" anywhere in its name would mean other people won't be\n   able to add hook that \"executes\" commands upon cues other than\n   receiving a push.\n\nI can guess why you chose that name, because I know there is a\nfunction called execute_commands() in \"git receive-pack\", but that\nis not somethhing you can expect your end users, who are not\nintimate to our codebase, to know.\n\n> We can use the external \"execute-commands\" hook to create pull requests\n> or send emails.\n\nYou can create pull requests or send emails out of the post-receive\nhook, so that is not a convincing justification why we want a new\nhook.\n\nNow, I understand that Gerrit-style \"notice a push to for/<target>,\ntake over the whole operation that happens after receiving the pack\ndata and do something entirely different, such as attempting to run\na merge with refs/heads/<target> and update refs/heads/<target>\ninstead, or fail the push if automerge fails\" is not easy to arrange\nwithin the current \"pre-receive\" + \"post-receive\" framework (by the\nway, we should start considering to deprecate \"update\", and\n\"post-update\" hooks as these \"*-receive\" hooks were added to replace\nthem, perhaps we should leave a #leftoverbits mark here).  And I\nthink it is reasonable to add a new hook that takes over the whole\nflow in \"git receive-pack\" to do so.\n\nI just do not think \"the execute-commands hook\" is a good name for\nit.  Perhaps \"divert-receive\" (as it diverts large portion of what\nreceive does) or something?  I dunno.\n\nHow do Gerrit folks deal with the \"we pushed to the server, so let's\npretend to have turned around and fetched from the same server\nimmediately after doing so\" client-side hack, by the way?  \n\nA vanilla \"git push\" on the client side does not know a push to\nrefs/for/master would result in an update to refs/heads/master on\nthe server side, and it would not know the result of the munging\ndone on the server side (whether it is to rebase what is received on\ntop of 'master' or to merge it to 'master') anyway, the\nremote-tracking branch refs/remotes/origin/master on the client side\nwould be left stale.  If we wanted to help them pretend to have\nfetched immediately after, I think we need to extend the protocol.\nRight now, after accepting \"git push\", the server end will say, for\neach proposed update for a ref, if the push updated successfully or\nnot, but to support the \"push to for/<target>, get heads/<target>\nupdated\" interaction, the reporting of the result (done in the\nreport() function in builtin/receive-pack.c) needs to be able to say\nwhat ref (it may be a ref that \"git push\" did not think it pushed\nto) got updated to what value (it may be an object the client does\nnot yet have---and we may have to actually turn around and fetch\nfrom them internally if we want to keep the illusion).\n\n\n"},{"id":"392883","messageId":"CANYiYbE2V3bLfEgH-aRDv4Y5V+_BTZn-oUN7fOVpARm+_14BdQ@mail.gmail.com","threadId":"52936","inReplyTo":"xmqqv9nj4zxk.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH 0/7] New execute-commands hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-05T16:51:19Z","receivedAt":"2020-03-05T16:51:33Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2020年3月5日周四 上午4:39写道：\n>\n> I do not claim to be great at naming, but you are worse ;-)\n\nI totally agree that I am not good at naming, for example my daughter's name.\n\n>  - Any hook is about executing command(s), so \"execute-commands\"\n>    hook does not give any information to users.\n>\n>  - IIUC, this is only about what happens when accepting a push and\n>    is not called at any other time.  Naming your hook without\n>    \"receive\" anywhere in its name would mean other people won't be\n>    able to add hook that \"executes\" commands upon cues other than\n>    receiving a push.\n>\n> I can guess why you chose that name, because I know there is a\n> function called execute_commands() in \"git receive-pack\", but that\n> is not somethhing you can expect your end users, who are not\n> intimate to our codebase, to know.\n\nYes, it's better to name the hook \"* -receive\", because the hooks are\nfor different commands, such as \"commit-msg\" is for `git commit`.\n\n> > We can use the external \"execute-commands\" hook to create pull requests\n> > or send emails.\n>\n> You can create pull requests or send emails out of the post-receive\n> hook, so that is not a convincing justification why we want a new\n> hook.\n\nAnother solution is using \"pre-receive\" + \"post-receive\" to handle a\npush to \"refs/for/master\".  The \"post-receive\" hook is used to create\na pull requst and delete the special reference \"refs/for/master\"\ncreated between these two hooks.  But having a temporary reference\ncreated is not safe for concurrent pushes.\n\n> Now, I understand that Gerrit-style \"notice a push to for/<target>,\n> take over the whole operation that happens after receiving the pack\n> data and do something entirely different, such as attempting to run\n> a merge with refs/heads/<target> and update refs/heads/<target>\n> instead, or fail the push if automerge fails\" is not easy to arrange\n> within the current \"pre-receive\" + \"post-receive\" framework (by the\n> way, we should start considering to deprecate \"update\", and\n> \"post-update\" hooks as these \"*-receive\" hooks were added to replace\n> them, perhaps we should leave a #leftoverbits mark here).  And I\n> think it is reasonable to add a new hook that takes over the whole\n> flow in \"git receive-pack\" to do so.\n>\n> I just do not think \"the execute-commands hook\" is a good name for\n> it.  Perhaps \"divert-receive\" (as it diverts large portion of what\n> receive does) or something?  I dunno.\n\nI suggest naming the hook as \"process-receive\", which is executed\nbetween the other two \"p*-receive\" hooks, and no need to create a\nspecial \"pre-receive\" for \"process-receive\".\n\n> How do Gerrit folks deal with the \"we pushed to the server, so let's\n> pretend to have turned around and fetched from the same server\n> immediately after doing so\" client-side hack, by the way?\n\nIn the following example, I push a local commit to a special reference\n(refs/for/master) of the remote Gerrit server.  The \"report()\"\nfunction (if Gerrit has one) says a new reference \"refs/for/master\"\nhas been created.  But in deed, there is no such reference created in\nthe remote repository, Gerrit will create another reference instead,\nsuch as \"refs/changes/71/623871/1\", for user to download the code\nreview .  Because the local repository only has normal\n\"remote.<name>.fetch\" config variables for remote tracking, so git\nwill not create a tracking reference for \"refs/for/master\".  Command\nline tool, such as Android \"repo\" (or the reimplemented git-repo in\nGolang), will create a special reference\n(refs/published/<local/branch>) for tracking, and these tools are\nresponsible for banch tracking.\n\n    $ git push --receive-pack=\"gerrit receive-pack\" origin\nrefs/heads/master:refs/for/master\n    Enumerating objects: 13, done.\n    Counting objects: 100% (13/13), done.\n    Delta compression using up to 8 threads\n    Compressing objects: 100% (11/11), done.\n    Writing objects: 100% (12/12), 1.34 KiB | 171.00 KiB/s, done.\n    Total 12 (delta 2), reused 0 (delta 0), pack-reused 0\n    remote: Resolving deltas: 100% (2/2)\n    remote: Processing changes: refs: 1, new: 1, done\n    remote:\n    remote: SUCCESS\n    remote:\n    remote: New Changes:\n    remote:   http://gerrit.example.com/c/my/repo/+/623889 Test commit\n    To ssh://gerrit.example.com:29418/my/repo\n     * [new branch]      master -> refs/for/master\n\n\n> A vanilla \"git push\" on the client side does not know a push to\n> refs/for/master would result in an update to refs/heads/master on\n> the server side, and it would not know the result of the munging\n> done on the server side (whether it is to rebase what is received on\n> top of 'master' or to merge it to 'master') anyway, the\n\nNeither Gerrit nor our AGit-Flow server will update the master branch.\nOur AGit-Flow server will create a special reference (like GitHub's\n\"refs/pull/<number>/head\") for reviewers to download commits.\n\n> remote-tracking branch refs/remotes/origin/master on the client side\n> would be left stale.  If we wanted to help them pretend to have\n> fetched immediately after, I think we need to extend the protocol.\n> Right now, after accepting \"git push\", the server end will say, for\n> each proposed update for a ref, if the push updated successfully or\n> not, but to support the \"push to for/<target>, get heads/<target>\n> updated\" interaction, the reporting of the result (done in the\n> report() function in builtin/receive-pack.c) needs to be able to say\n> what ref (it may be a ref that \"git push\" did not think it pushed\n> to) got updated to what value (it may be an object the client does\n> not yet have---and we may have to actually turn around and fetch\n> from them internally if we want to keep the illusion).\n\nI have no idea now how to make a simple patch to give an accurate report.\n"},{"id":"392943","messageId":"20200308145631.64040-1-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"CANYiYbE2V3bLfEgH-aRDv4Y5V+_BTZn-oUN7fOVpARm+_14BdQ@mail.gmail.com","subject":"[PATCH v2 0/5] New proc-receive hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-08T14:56:26Z","receivedAt":"2020-03-08T14:57:52Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Changes since v1:\n\n* Use a better name \"proc-receive\" for the new hook.\n* Rename the new config variable to \"receive.procReceiveRefs\".\n\n--\n\nJiang Xin (5):\n  receive-pack: add new proc-receive hook\n  refs.c: refactor to reuse ref_is_hidden()\n  receive-pack: new config receive.procReceiveRefs\n  receive-pack: read env from proc-receive output\n  hook: add document and example for \"proc-receive\" hook\n\n Documentation/config/receive.txt     |  14 +\n Documentation/githooks.txt           |  22 +\n builtin/receive-pack.c               | 160 +++++-\n refs.c                               |  11 +-\n refs.h                               |   1 +\n t/t5411-proc-receive-hook.sh         | 831 +++++++++++++++++++++++++++\n templates/hooks--proc-receive.sample | 119 ++++\n 7 files changed, 1141 insertions(+), 17 deletions(-)\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100755 templates/hooks--proc-receive.sample\n\n-- \n2.26.0.rc0.5.gb02b988a14.dirty\n\n"},{"id":"392944","messageId":"20200308145631.64040-2-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"CANYiYbE2V3bLfEgH-aRDv4Y5V+_BTZn-oUN7fOVpARm+_14BdQ@mail.gmail.com","subject":"[PATCH v2 1/5] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-08T14:56:27Z","receivedAt":"2020-03-08T14:57:59Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Git calls an internal `execute_commands` function to handle commands\nsent from client to `git-receive-pack`.  Regardless of what references\nthe user pushes, git creates or updates the corresponding references if\nthe user has write-permission.  A contributor who has no\nwrite-permission, cannot push to the repository directly.  So, the\ncontributor has to write commits to an alternate location, and sends\npull request by emails or by other ways.  We call this workflow as a\ndistributed workflow.\n\nIt would be more convenient to work in a centralized workflow like what\nGerrit provided for some cases.  For example, a read-only user who\ncannot push to a branch directly can run the following `git push`\ncommand to push commits to a pseudo reference (has a prefix \"refs/for/\",\nnot \"refs/heads/\") to create a code review.\n\n    git push origin \\\n        HEAD:refs/for/<branch-name>/<session>\n\nThe `<branch-name>` in the above example can be as simple as \"master\",\nor a more complicated branch name like \"foo/bar\".  The `<session>` in\nthe above example command can be the local branch name of the client\nside, such as \"my/topic\".\n\nWe cannot implement a centralized workflow elegantly by using\n\"pre-receive\" + \"post-receive\", because Git will call the internal\nfunction \"execute_commands\" to create references (even the special\npseudo reference) between these two hooks.  Even though we can delete\nthe temporarily created pseudo reference via the \"post-receive\" hook,\nhaving a temporary reference is not safe for concurrent pushes.\n\nSo, add a filter and a new handler to support this kind of workflow.\nThe filter will check the prefix of the reference name, and if the\ncommand has a special reference name, the filter will turn a specific\nfield (`have_special_ref`) on for the command.  Commands with this filed\nturned on will be executed by a new handler (an hook named\n\"proc-receive\") instead of the internal `execute_commands` function.\nWe can use this \"proc-receive\" command to create pull requests or send\nemails for code review.\n\nThis \"proc-receive\" hook executes once for the receive operation. It\ntakes no arguments, but gets the same information as the pre-receive\nhook does on its standard input.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c       |  93 +++++-\n t/t5411-proc-receive-hook.sh | 547 +++++++++++++++++++++++++++++++++++\n 2 files changed, 628 insertions(+), 12 deletions(-)\n create mode 100755 t/t5411-proc-receive-hook.sh\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 2cc18bbffd..23d0c224d2 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -312,7 +312,8 @@ struct command {\n \tstruct command *next;\n \tconst char *error_string;\n \tunsigned int skip_update:1,\n-\t\t     did_not_exist:1;\n+\t\t     did_not_exist:1,\n+\t\t     have_special_ref:1;\n \tint index;\n \tstruct object_id old_oid;\n \tstruct object_id new_oid;\n@@ -669,6 +670,9 @@ static void prepare_push_cert_sha1(struct child_process *proc)\n \n struct receive_hook_feed_state {\n \tstruct command *cmd;\n+\tint feed_normal_ref;\n+\tint feed_special_ref;\n+\tint hook_must_exist;\n \tint skip_broken;\n \tstruct strbuf buf;\n \tconst struct string_list *push_options;\n@@ -684,8 +688,14 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n \tint code;\n \n \targv[0] = find_hook(hook_name);\n-\tif (!argv[0])\n-\t\treturn 0;\n+\tif (!argv[0]) {\n+\t\tif (feed_state->hook_must_exist) {\n+\t\t\trp_error(\"cannot to find hook '%s'\", hook_name);\n+\t\t\treturn 1;\n+\t\t} else {\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n \n \targv[1] = NULL;\n \n@@ -751,9 +761,17 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n \tstruct receive_hook_feed_state *state = state_;\n \tstruct command *cmd = state->cmd;\n \n-\twhile (cmd &&\n-\t       state->skip_broken && (cmd->error_string || cmd->did_not_exist))\n-\t\tcmd = cmd->next;\n+\twhile (cmd) {\n+\t\tif (state->skip_broken && (cmd->error_string || cmd->did_not_exist))\n+\t\t\tcmd = cmd->next;\n+\t\telse if (state->feed_special_ref && cmd->have_special_ref)\n+\t\t\tbreak;\n+\t\telse if (state->feed_normal_ref && !cmd->have_special_ref)\n+\t\t\tbreak;\n+\t\telse\n+\t\t\tcmd = cmd->next;\n+\t}\n+\n \tif (!cmd)\n \t\treturn -1; /* EOF */\n \tstrbuf_reset(&state->buf);\n@@ -778,6 +796,9 @@ static int run_receive_hook(struct command *commands,\n \n \tstrbuf_init(&state.buf, 0);\n \tstate.cmd = commands;\n+\tstate.feed_normal_ref = 1;\n+\tstate.feed_special_ref = 1;\n+\tstate.hook_must_exist = 0;\n \tstate.skip_broken = skip_broken;\n \tif (feed_receive_hook(&state, NULL, NULL))\n \t\treturn 0;\n@@ -817,6 +838,27 @@ static int run_update_hook(struct command *cmd)\n \treturn finish_command(&proc);\n }\n \n+static int run_proc_receive_hook(struct command *commands,\n+\t\t\t\t const struct string_list *push_options)\n+{\n+\tstruct receive_hook_feed_state state;\n+\tint status;\n+\n+\tstrbuf_init(&state.buf, 0);\n+\tstate.cmd = commands;\n+\tstate.feed_normal_ref = 0;\n+\tstate.feed_special_ref = 1;\n+\tstate.hook_must_exist = 1;\n+\tstate.skip_broken = 1;\n+\tif (feed_receive_hook(&state, NULL, NULL))\n+\t\treturn 0;\n+\tstate.cmd = commands;\n+\tstate.push_options = push_options;\n+\tstatus = run_and_feed_hook(\"proc-receive\", feed_receive_hook, &state);\n+\tstrbuf_release(&state.buf);\n+\treturn status;\n+}\n+\n static char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n@@ -1392,7 +1434,7 @@ static void execute_commands_non_atomic(struct command *commands,\n \tstruct strbuf err = STRBUF_INIT;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->have_special_ref)\n \t\t\tcontinue;\n \n \t\ttransaction = ref_transaction_begin(&err);\n@@ -1432,7 +1474,7 @@ static void execute_commands_atomic(struct command *commands,\n \t}\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->have_special_ref)\n \t\t\tcontinue;\n \n \t\tcmd->error_string = update(cmd, si);\n@@ -1468,6 +1510,8 @@ static void execute_commands(struct command *commands,\n \tstruct iterate_data data;\n \tstruct async muxer;\n \tint err_fd = 0;\n+\tint have_special_ref = 0;\n+\tint have_normal_ref = 0;\n \n \tif (unpacker_error) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n@@ -1497,6 +1541,22 @@ static void execute_commands(struct command *commands,\n \n \treject_updates_to_hidden(commands);\n \n+\t/* Try to find commands that have special prefix in their reference names,\n+\t * and mark them to run an external \"proc-receive\" hook later.\n+\t */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!should_process_cmd(cmd))\n+\t\t\tcontinue;\n+\n+\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n+\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n+\t\t\tcmd->have_special_ref = 1;\n+\t\t\thave_special_ref = 1;\n+\t\t} else {\n+\t\t\thave_normal_ref = 1;\n+\t\t}\n+\t}\n+\n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n \t\t\tif (!cmd->error_string)\n@@ -1523,10 +1583,19 @@ static void execute_commands(struct command *commands,\n \tfree(head_name_to_free);\n \thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, NULL, NULL);\n \n-\tif (use_atomic)\n-\t\texecute_commands_atomic(commands, si);\n-\telse\n-\t\texecute_commands_non_atomic(commands, si);\n+\tif (have_special_ref && run_proc_receive_hook(commands, push_options)) {\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tif (!cmd->error_string  && (cmd->have_special_ref || use_atomic))\n+\t\t\t\tcmd->error_string = \"fail to run proc-receive hook\";\n+\t\t}\n+\t}\n+\n+\tif (have_normal_ref) {\n+\t\tif (use_atomic)\n+\t\t\texecute_commands_atomic(commands, si);\n+\t\telse\n+\t\t\texecute_commands_non_atomic(commands, si);\n+\t}\n \n \tif (shallow_update)\n \t\twarn_if_skipped_connectivity_check(commands, si);\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nnew file mode 100755\nindex 0000000000..01f67965bf\n--- /dev/null\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -0,0 +1,547 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2018-2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook on special git-push refspec'\n+\n+. ./test-lib.sh\n+\n+bare=bare.git\n+\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{} 2>/dev/null)\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+test_expect_success \"setup repository\" '\n+\tgit init --bare $bare &&\n+\n+\t# Enable push options for bare.git.\n+\tgit -C $bare config receive.advertisePushOptions true &&\n+\n+\tgit clone --no-local $bare work &&\n+\tcreate_commits_in work A B\n+'\n+\n+test_expect_success \"setup hooks\" '\n+\t## pre-receive hook\n+\tcat >$bare/hooks/pre-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"execute: pre-receive hook\\n\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\tprintf >&2 \">> old: \\$old, new: \\$new, ref: \\$ref.\\n\"\n+\tdone\n+\tEOF\n+\n+\t## proc-receive hook\n+\tcat >$bare/hooks/proc-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"execute: proc-receive hook\\n\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\tprintf >&2 \">> old: \\$old, new: \\$new, ref: \\$ref.\\n\"\n+\tdone\n+\tEOF\n+\n+\t## post-receive hook\n+\tcat >$bare/hooks/post-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"execute: post-receive hook\\n\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\tprintf >&2 \">> old: \\$old, new: \\$new, ref: \\$ref.\\n\"\n+\tdone\n+\tEOF\n+\tchmod a+x \\\n+\t\t$bare/hooks/pre-receive \\\n+\t\t$bare/hooks/post-receive \\\n+\t\t$bare/hooks/proc-receive \\\n+'\n+\n+test_expect_success \"(1) standard git-push command\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit update-ref HEAD $A &&\n+\t\tgit push origin HEAD HEAD:maint HEAD:a/b/c 2>&1\n+\t) >out &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: $ZERO_OID, new: $A, ref: refs/heads/master.\n+\tremote: >> old: $ZERO_OID, new: $A, ref: refs/heads/maint.\n+\tremote: >> old: $ZERO_OID, new: $A, ref: refs/heads/a/b/c.\n+\tremote: execute: post-receive hook\n+\tremote: >> old: $ZERO_OID, new: $A, ref: refs/heads/master.\n+\tremote: >> old: $ZERO_OID, new: $A, ref: refs/heads/maint.\n+\tremote: >> old: $ZERO_OID, new: $A, ref: refs/heads/a/b/c.\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd $bare &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t$A refs/heads/a/b/c\n+\t$A refs/heads/maint\n+\t$A refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(1) push one special ref\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit update-ref HEAD $B &&\n+\t\tgit push origin HEAD:refs/for/master/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/master/my/topic.\n+\tremote: execute: proc-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/master/my/topic.\n+\tremote: execute: post-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/master/my/topic.\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd $bare &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t$A refs/heads/a/b/c\n+\t$A refs/heads/maint\n+\t$A refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(1) push both a normal and a special refs\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit push origin \\\n+\t\t\tHEAD:refs/for/maint/my/topic \\\n+\t\t\tHEAD:refs/heads/master\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: $A, new: $B, ref: refs/heads/master.\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/maint/my/topic.\n+\tremote: execute: proc-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/maint/my/topic.\n+\tremote: execute: post-receive hook\n+\tremote: >> old: $A, new: $B, ref: refs/heads/master.\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/maint/my/topic.\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd $bare &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t$A refs/heads/a/b/c\n+\t$A refs/heads/maint\n+\t$B refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(1) cleanup\" '\n+\t(\n+\t\tcd $bare &&\n+\t\tgit update-ref refs/heads/master $A $B &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t$A refs/heads/a/b/c\n+\t$A refs/heads/maint\n+\t$A refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(2) remove proc-receive hook\" '\n+\tmv $bare/hooks/proc-receive $bare/hooks/proc-receive.ok\n+'\n+\n+test_expect_success \"(2) standard git-push command\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit update-ref HEAD $B &&\n+\t\tgit push origin HEAD:master 2>&1\n+\t) >out &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: $A, new: $B, ref: refs/heads/master.\n+\tremote: execute: post-receive hook\n+\tremote: >> old: $A, new: $B, ref: refs/heads/master.\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd $bare &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t$A refs/heads/a/b/c\n+\t$A refs/heads/maint\n+\t$B refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(2) cleanup\" '\n+\t(\n+\t\tcd $bare &&\n+\t\tgit update-ref refs/heads/master $A $B &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t$A refs/heads/a/b/c\n+\t$A refs/heads/maint\n+\t$A refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(2) push one special ref (failed)\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit update-ref HEAD $B &&\n+\t\ttest_must_fail git push origin HEAD:refs/for/a/b/c/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/a/b/c/my/topic.\n+\tremote: error: cannot to find hook '\"'\"'proc-receive'\"'\"'\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(2) push both a normal and a special refs (one failed)\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit update-ref HEAD $B &&\n+\t\ttest_must_fail git push origin \\\n+\t\t\tHEAD:refs/for/maint/my/topic \\\n+\t\t\tHEAD:refs/heads/master\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: $A, new: $B, ref: refs/heads/master.\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/maint/my/topic.\n+\tremote: error: cannot to find hook '\"'\"'proc-receive'\"'\"'\n+\tremote: execute: post-receive hook\n+\tremote: >> old: $A, new: $B, ref: refs/heads/master.\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd $bare &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t$A refs/heads/a/b/c\n+\t$A refs/heads/maint\n+\t$B refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(2) cleanup\" '\n+\t(\n+\t\tcd $bare &&\n+\t\tgit update-ref refs/heads/master $A $B &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t$A refs/heads/a/b/c\n+\t$A refs/heads/maint\n+\t$A refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(2) atomic push both a normal and a special refs (failed)\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit update-ref HEAD $B &&\n+\t\ttest_must_fail git push --atomic origin \\\n+\t\t\tHEAD:refs/for/maint/my/topic \\\n+\t\t\tHEAD:refs/heads/master\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: $A, new: $B, ref: refs/heads/master.\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/maint/my/topic.\n+\tremote: error: cannot to find hook '\"'\"'proc-receive'\"'\"'\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd $bare &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t$A refs/heads/a/b/c\n+\t$A refs/heads/maint\n+\t$A refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(3) new proc-receive hook (return error)\" '\n+\tcat >$bare/hooks/proc-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"execute: proc-receive hook\\n\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\tprintf >&2 \">> old: \\$old, new: \\$new, ref: \\$ref.\\n\"\n+\tdone\n+\n+\tprintf >&2 \"fail to run proc-receive\\n\"\n+\texit 1\n+\tEOF\n+\tchmod a+x $bare/hooks/proc-receive\n+'\n+\n+test_expect_success \"(3) standard git-push command\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit update-ref HEAD $B &&\n+\t\tgit push origin HEAD:master 2>&1\n+\t) >out &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: $A, new: $B, ref: refs/heads/master.\n+\tremote: execute: post-receive hook\n+\tremote: >> old: $A, new: $B, ref: refs/heads/master.\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd $bare &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t$A refs/heads/a/b/c\n+\t$A refs/heads/maint\n+\t$B refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(3) cleanup\" '\n+\t(\n+\t\tcd $bare &&\n+\t\tgit update-ref refs/heads/master $A $B &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t$A refs/heads/a/b/c\n+\t$A refs/heads/maint\n+\t$A refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(3) push both a normal and a special refs (one failed)\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit update-ref HEAD $B &&\n+\t\ttest_must_fail git push origin \\\n+\t\t\tHEAD:refs/for/maint/my/topic \\\n+\t\t\tHEAD:refs/heads/master\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: $A, new: $B, ref: refs/heads/master.\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/maint/my/topic.\n+\tremote: execute: proc-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/maint/my/topic.\n+\tremote: fail to run proc-receive\n+\tremote: execute: post-receive hook\n+\tremote: >> old: $A, new: $B, ref: refs/heads/master.\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd $bare &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t$A refs/heads/a/b/c\n+\t$A refs/heads/maint\n+\t$B refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(3) cleanup\" '\n+\t(\n+\t\tcd $bare &&\n+\t\tgit update-ref refs/heads/master $A $B &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t$A refs/heads/a/b/c\n+\t$A refs/heads/maint\n+\t$A refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(3) atomic push a normal and a special refs (failed)\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit update-ref HEAD $B &&\n+\t\ttest_must_fail git push --atomic origin \\\n+\t\t\tHEAD:refs/for/maint/my/topic \\\n+\t\t\tHEAD:refs/heads/master\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: $A, new: $B, ref: refs/heads/master.\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/maint/my/topic.\n+\tremote: execute: proc-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/maint/my/topic.\n+\tremote: fail to run proc-receive\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd $bare &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t$A refs/heads/a/b/c\n+\t$A refs/heads/maint\n+\t$A refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(4) restore proc-receive hook\" '\n+\tmv $bare/hooks/proc-receive $bare/hooks/proc-receive.fail &&\n+\tmv $bare/hooks/proc-receive.ok $bare/hooks/proc-receive\n+'\n+\n+test_expect_success \"(4) push two special references\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit update-ref HEAD $B &&\n+\t\tgit push origin \\\n+\t\t\tHEAD:refs/for/maint/my/topic \\\n+\t\t\tHEAD:refs/for/a/b/c/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/maint/my/topic.\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/a/b/c/my/topic.\n+\tremote: execute: proc-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/maint/my/topic.\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/a/b/c/my/topic.\n+\tremote: execute: post-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/maint/my/topic.\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/a/b/c/my/topic.\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd $bare &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t$A refs/heads/a/b/c\n+\t$A refs/heads/maint\n+\t$A refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(5) new pre-receive hook hook (return error)\" '\n+\tmv $bare/hooks/pre-receive $bare/hooks/pre-receive.ok &&\n+\tcat >$bare/hooks/pre-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"execute: pre-receive hook\\n\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\tprintf >&2 \">> old: \\$old, new: \\$new, ref: \\$ref.\\n\"\n+\tdone\n+\n+\tprintf >&2 \">> ERROR: declined in pre-receive hook\\n\"\n+\texit 1\n+\tEOF\n+\tchmod a+x $bare/hooks/pre-receive\n+'\n+\n+test_expect_success \"(5) push two special references (declined)\" '\n+\t(\n+\t\tcd work &&\n+\t\ttest_must_fail git push origin \\\n+\t\t\tHEAD:refs/for/master/my/topic \\\n+\t\t\tHEAD:refs/for/maint/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/master/my/topic.\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/maint/my/topic.\n+\tremote: >> ERROR: declined in pre-receive hook\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(5) push both a normal and a special refs (declined)\" '\n+\t(\n+\t\tcd work &&\n+\t\ttest_must_fail git push origin \\\n+\t\t\tHEAD:refs/for/master/my/topic \\\n+\t\t\tHEAD:refs/heads/master\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: $A, new: $B, ref: refs/heads/master.\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/master/my/topic.\n+\tremote: >> ERROR: declined in pre-receive hook\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \n2.26.0.rc0.5.gb02b988a14.dirty\n\n"},{"id":"392945","messageId":"20200308145631.64040-3-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"CANYiYbE2V3bLfEgH-aRDv4Y5V+_BTZn-oUN7fOVpARm+_14BdQ@mail.gmail.com","subject":"[PATCH v2 2/5] refs.c: refactor to reuse ref_is_hidden()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-08T14:56:28Z","receivedAt":"2020-03-08T14:58:01Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Add new function `ref_is_matched()` to reuse `ref_is_hidden()`. Will use\nthis function for `receive-pack` to check commands with specific\nprefixes.\n\nTest case t5512 covered this change.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n refs.c | 11 ++++++++---\n refs.h |  1 +\n 2 files changed, 9 insertions(+), 3 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 1ab0bb54d3..229159ea1a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1389,13 +1389,18 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti\n }\n \n int ref_is_hidden(const char *refname, const char *refname_full)\n+{\n+\treturn ref_is_matched(hide_refs, refname, refname_full);\n+}\n+\n+int ref_is_matched(struct string_list *match_refs, const char *refname, const char *refname_full)\n {\n \tint i;\n \n-\tif (!hide_refs)\n+\tif (!match_refs)\n \t\treturn 0;\n-\tfor (i = hide_refs->nr - 1; i >= 0; i--) {\n-\t\tconst char *match = hide_refs->items[i].string;\n+\tfor (i = match_refs->nr - 1; i >= 0; i--) {\n+\t\tconst char *match = match_refs->items[i].string;\n \t\tconst char *subject;\n \t\tint neg = 0;\n \t\tconst char *p;\ndiff --git a/refs.h b/refs.h\nindex 545029c6d8..a2ea043f7f 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -739,6 +739,7 @@ int parse_hide_refs_config(const char *var, const char *value, const char *);\n  * parameter always points to the full ref name.\n  */\n int ref_is_hidden(const char *, const char *);\n+int ref_is_matched(struct string_list *, const char *, const char *);\n \n enum ref_type {\n \tREF_TYPE_PER_WORKTREE,\t  /* refs inside refs/ but not shared       */\n-- \n2.26.0.rc0.5.gb02b988a14.dirty\n\n"},{"id":"392946","messageId":"20200308153816.4690-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"CANYiYbE2V3bLfEgH-aRDv4Y5V+_BTZn-oUN7fOVpARm+_14BdQ@mail.gmail.com","subject":"[PATCH v2 3/5] receive-pack: new config receive.procReceiveRefs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-08T15:38:14Z","receivedAt":"2020-03-08T15:38:23Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd a new multi-valued config variable \"receive.procReceiveRefs\"\nfor `receive-pack` command, like the follows:\n\n    git config --system --add receive.procReceiveRefs refs/for/\n    git config --system --add receive.procReceiveRefs refs/drafts/\n\nIf the specific prefix strings match the reference names of the commands\nwhich are sent by git client to `receive-pack`, these commands will be\nexecuted by an external hook (named \"proc-receive\"), instead of the\ninternal `execute_commands` function.\n\nFor example, if it is set to \"refs/for/\", pushing to a reference such as\n\"refs/for/master\" will not create or update reference \"refs/for/master\",\nbut may create or update a pull request directly by running the external\nhook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/config/receive.txt |  14 +++\n builtin/receive-pack.c           |  49 ++++++--\n t/t5411-proc-receive-hook.sh     | 189 +++++++++++++++++++++++++++----\n 3 files changed, 220 insertions(+), 32 deletions(-)\n\ndiff --git a/Documentation/config/receive.txt b/Documentation/config/receive.txt\nindex 65f78aac37..0178f2d478 100644\n--- a/Documentation/config/receive.txt\n+++ b/Documentation/config/receive.txt\n@@ -114,6 +114,20 @@ receive.hideRefs::\n \tAn attempt to update or delete a hidden ref by `git push` is\n \trejected.\n \n+receive.procReceiveRefs::\n+\tThis is a multi-valued variable that defines reference prefixes\n+\tto match the commands in `receive-pack`.  Commands matching the\n+\tprefixes will be executed by an external hooks \"proc-receive\",\n+\tinstead of the internal `execute_commands` function.  If this\n+\tvariable is not defined, the \"proc-receive\" hook will never be\n+\tused, and all commands will be executed by the internal\n+\t`execute_commands` function.\n+\n+\tFor example, if this variable is set to \"refs/for/\", pushing to\n+\treference such as \"refs/for/master\" will not create or update a\n+\treference named \"refs/for/master\", but may create or update a\n+\tpull request directly by running an external hook.\n+\n receive.updateServerInfo::\n \tIf set to true, git-receive-pack will run git-update-server-info\n \tafter receiving data from git-push and updating refs.\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 23d0c224d2..5aff682758 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -76,6 +76,7 @@ static struct object_id push_cert_oid;\n static struct signature_check sigcheck;\n static const char *push_cert_nonce;\n static const char *cert_nonce_seed;\n+static struct string_list proc_receive_refs;\n \n static const char *NONCE_UNSOLICITED = \"UNSOLICITED\";\n static const char *NONCE_BAD = \"BAD\";\n@@ -228,6 +229,20 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (strcmp(var, \"receive.procreceiverefs\") == 0) {\n+\t\tchar *prefix;\n+\t\tint len;\n+\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tprefix = xstrdup(value);\n+\t\tlen = strlen(prefix);\n+\t\twhile (len && prefix[len - 1] == '/')\n+\t\t\tprefix[--len] = '\\0';\n+\t\tstring_list_insert(&proc_receive_refs, prefix);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -1544,17 +1559,30 @@ static void execute_commands(struct command *commands,\n \t/* Try to find commands that have special prefix in their reference names,\n \t * and mark them to run an external \"proc-receive\" hook later.\n \t */\n-\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n-\t\t\tcontinue;\n+\tif (proc_receive_refs.nr > 0) {\n+\t\tstruct strbuf refname_full = STRBUF_INIT;\n+\t\tsize_t prefix_len;\n \n-\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n-\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n-\t\t\tcmd->have_special_ref = 1;\n-\t\t\thave_special_ref = 1;\n-\t\t} else {\n-\t\t\thave_normal_ref = 1;\n+\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n+\t\tprefix_len = refname_full.len;\n+\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tif (!should_process_cmd(cmd))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_setlen(&refname_full, prefix_len);\n+\t\t\tstrbuf_addstr(&refname_full, cmd->ref_name);\n+\t\t\tif (ref_is_matched(&proc_receive_refs, cmd->ref_name, refname_full.buf)) {\n+\t\t\t\tcmd->have_special_ref = 1;\n+\t\t\t\thave_special_ref = 1;\n+\t\t\t} else {\n+\t\t\t\thave_normal_ref = 1;\n+\t\t\t}\n \t\t}\n+\n+\t\tstrbuf_release(&refname_full);\n+\t} else {\n+\t\thave_normal_ref = 1;\n \t}\n \n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n@@ -2011,6 +2039,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n+\tstring_list_init(&proc_receive_refs, 0);\n+\n \tpacket_trace_identity(\"receive-pack\");\n \n \targc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);\n@@ -2126,5 +2156,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \toid_array_clear(&shallow);\n \toid_array_clear(&ref);\n \tfree((void *)push_cert_nonce);\n+\tstring_list_clear(&proc_receive_refs, 0);\n \treturn 0;\n }\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex 01f67965bf..e3bb421078 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -91,17 +91,77 @@ test_expect_success \"(1) standard git-push command\" '\n \t(\n \t\tcd work &&\n \t\tgit update-ref HEAD $A &&\n-\t\tgit push origin HEAD HEAD:maint HEAD:a/b/c 2>&1\n+\t\tgit push origin HEAD HEAD:maint 2>&1\n \t) >out &&\n \tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n \tcat >expect <<-EOF &&\n \tremote: execute: pre-receive hook\n \tremote: >> old: $ZERO_OID, new: $A, ref: refs/heads/master.\n \tremote: >> old: $ZERO_OID, new: $A, ref: refs/heads/maint.\n-\tremote: >> old: $ZERO_OID, new: $A, ref: refs/heads/a/b/c.\n \tremote: execute: post-receive hook\n \tremote: >> old: $ZERO_OID, new: $A, ref: refs/heads/master.\n \tremote: >> old: $ZERO_OID, new: $A, ref: refs/heads/maint.\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd $bare &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t$A refs/heads/maint\n+\t$A refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(1) push one special ref (create one)\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit update-ref HEAD $B &&\n+\t\tgit push origin HEAD:refs/for/master/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/master/my/topic.\n+\tremote: execute: post-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/master/my/topic.\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd $bare &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t$B refs/for/master/my/topic\n+\t$A refs/heads/maint\n+\t$A refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(1) cleanup\" '\n+\t(\n+\t\tcd $bare &&\n+\t\tgit update-ref -d refs/for/master/my/topic\n+\t)\n+'\n+\n+test_expect_success \"(2) add config to turn on proc-receive\" '\n+\tgit -C $bare config --add receive.procReceiveRefs refs/for/\n+'\n+\n+test_expect_success \"(2) standard git-push command\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit update-ref HEAD $A &&\n+\t\tgit push origin HEAD:a/b/c 2>&1\n+\t) >out &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: $ZERO_OID, new: $A, ref: refs/heads/a/b/c.\n+\tremote: execute: post-receive hook\n \tremote: >> old: $ZERO_OID, new: $A, ref: refs/heads/a/b/c.\n \tEOF\n \ttest_cmp expect actual &&\n@@ -117,7 +177,7 @@ test_expect_success \"(1) standard git-push command\" '\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"(1) push one special ref\" '\n+test_expect_success \"(2) push one special ref\" '\n \t(\n \t\tcd work &&\n \t\tgit update-ref HEAD $B &&\n@@ -145,9 +205,10 @@ test_expect_success \"(1) push one special ref\" '\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"(1) push both a normal and a special refs\" '\n+test_expect_success \"(2) push both a normal and a special refs\" '\n \t(\n \t\tcd work &&\n+\t\tgit update-ref HEAD $B &&\n \t\tgit push origin \\\n \t\t\tHEAD:refs/for/maint/my/topic \\\n \t\t\tHEAD:refs/heads/master\n@@ -176,7 +237,7 @@ test_expect_success \"(1) push both a normal and a special refs\" '\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"(1) cleanup\" '\n+test_expect_success \"(2) cleanup\" '\n \t(\n \t\tcd $bare &&\n \t\tgit update-ref refs/heads/master $A $B &&\n@@ -190,11 +251,93 @@ test_expect_success \"(1) cleanup\" '\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"(2) remove proc-receive hook\" '\n+test_expect_success \"(2) push two special references (one is not registered)\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit push origin \\\n+\t\t\tHEAD:refs/for/maint/my/topic \\\n+\t\t\tHEAD:refs/drafts/maint/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/maint/my/topic.\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/drafts/maint/my/topic.\n+\tremote: execute: proc-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/maint/my/topic.\n+\tremote: execute: post-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/maint/my/topic.\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/drafts/maint/my/topic.\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd $bare &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t$B refs/drafts/maint/my/topic\n+\t$A refs/heads/a/b/c\n+\t$A refs/heads/maint\n+\t$A refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(2) cleanup\" '\n+\t(\n+\t\tcd $bare &&\n+\t\tgit update-ref -d refs/drafts/maint/my/topic &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t$A refs/heads/a/b/c\n+\t$A refs/heads/maint\n+\t$A refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(2) register new ref prefix\" '\n+\tgit -C $bare config --add receive.procReceiveRefs refs/drafts/\n+'\n+\n+test_expect_success \"(2) push to two special references (all registered)\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit push origin \\\n+\t\t\tHEAD:refs/for/master/my/topic \\\n+\t\t\tHEAD:refs/drafts/maint/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/master/my/topic.\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/drafts/maint/my/topic.\n+\tremote: execute: proc-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/master/my/topic.\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/drafts/maint/my/topic.\n+\tremote: execute: post-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/master/my/topic.\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/drafts/maint/my/topic.\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd $bare &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t$A refs/heads/a/b/c\n+\t$A refs/heads/maint\n+\t$A refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(3) remove proc-receive hook\" '\n \tmv $bare/hooks/proc-receive $bare/hooks/proc-receive.ok\n '\n \n-test_expect_success \"(2) standard git-push command\" '\n+test_expect_success \"(3) standard git-push command\" '\n \t(\n \t\tcd work &&\n \t\tgit update-ref HEAD $B &&\n@@ -220,7 +363,7 @@ test_expect_success \"(2) standard git-push command\" '\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"(2) cleanup\" '\n+test_expect_success \"(3) cleanup\" '\n \t(\n \t\tcd $bare &&\n \t\tgit update-ref refs/heads/master $A $B &&\n@@ -234,7 +377,7 @@ test_expect_success \"(2) cleanup\" '\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"(2) push one special ref (failed)\" '\n+test_expect_success \"(3) push one special ref (failed)\" '\n \t(\n \t\tcd work &&\n \t\tgit update-ref HEAD $B &&\n@@ -249,7 +392,7 @@ test_expect_success \"(2) push one special ref (failed)\" '\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"(2) push both a normal and a special refs (one failed)\" '\n+test_expect_success \"(3) push both a normal and a special refs (one failed)\" '\n \t(\n \t\tcd work &&\n \t\tgit update-ref HEAD $B &&\n@@ -279,7 +422,7 @@ test_expect_success \"(2) push both a normal and a special refs (one failed)\" '\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"(2) cleanup\" '\n+test_expect_success \"(3) cleanup\" '\n \t(\n \t\tcd $bare &&\n \t\tgit update-ref refs/heads/master $A $B &&\n@@ -293,7 +436,7 @@ test_expect_success \"(2) cleanup\" '\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"(2) atomic push both a normal and a special refs (failed)\" '\n+test_expect_success \"(3) atomic push both a normal and a special refs (failed)\" '\n \t(\n \t\tcd work &&\n \t\tgit update-ref HEAD $B &&\n@@ -321,7 +464,7 @@ test_expect_success \"(2) atomic push both a normal and a special refs (failed)\"\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"(3) new proc-receive hook (return error)\" '\n+test_expect_success \"(4) new proc-receive hook (return error)\" '\n \tcat >$bare/hooks/proc-receive <<-EOF &&\n \t#!/bin/sh\n \n@@ -338,7 +481,7 @@ test_expect_success \"(3) new proc-receive hook (return error)\" '\n \tchmod a+x $bare/hooks/proc-receive\n '\n \n-test_expect_success \"(3) standard git-push command\" '\n+test_expect_success \"(4) standard git-push command\" '\n \t(\n \t\tcd work &&\n \t\tgit update-ref HEAD $B &&\n@@ -364,7 +507,7 @@ test_expect_success \"(3) standard git-push command\" '\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"(3) cleanup\" '\n+test_expect_success \"(4) cleanup\" '\n \t(\n \t\tcd $bare &&\n \t\tgit update-ref refs/heads/master $A $B &&\n@@ -378,7 +521,7 @@ test_expect_success \"(3) cleanup\" '\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"(3) push both a normal and a special refs (one failed)\" '\n+test_expect_success \"(4) push both a normal and a special refs (one failed)\" '\n \t(\n \t\tcd work &&\n \t\tgit update-ref HEAD $B &&\n@@ -410,7 +553,7 @@ test_expect_success \"(3) push both a normal and a special refs (one failed)\" '\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"(3) cleanup\" '\n+test_expect_success \"(4) cleanup\" '\n \t(\n \t\tcd $bare &&\n \t\tgit update-ref refs/heads/master $A $B &&\n@@ -424,7 +567,7 @@ test_expect_success \"(3) cleanup\" '\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"(3) atomic push a normal and a special refs (failed)\" '\n+test_expect_success \"(4) atomic push a normal and a special refs (failed)\" '\n \t(\n \t\tcd work &&\n \t\tgit update-ref HEAD $B &&\n@@ -454,12 +597,12 @@ test_expect_success \"(3) atomic push a normal and a special refs (failed)\" '\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"(4) restore proc-receive hook\" '\n+test_expect_success \"(5) restore proc-receive hook\" '\n \tmv $bare/hooks/proc-receive $bare/hooks/proc-receive.fail &&\n \tmv $bare/hooks/proc-receive.ok $bare/hooks/proc-receive\n '\n \n-test_expect_success \"(4) push two special references\" '\n+test_expect_success \"(5) push two special references\" '\n \t(\n \t\tcd work &&\n \t\tgit update-ref HEAD $B &&\n@@ -492,7 +635,7 @@ test_expect_success \"(4) push two special references\" '\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"(5) new pre-receive hook hook (return error)\" '\n+test_expect_success \"(6) new pre-receive hook hook (return error)\" '\n \tmv $bare/hooks/pre-receive $bare/hooks/pre-receive.ok &&\n \tcat >$bare/hooks/pre-receive <<-EOF &&\n \t#!/bin/sh\n@@ -510,7 +653,7 @@ test_expect_success \"(5) new pre-receive hook hook (return error)\" '\n \tchmod a+x $bare/hooks/pre-receive\n '\n \n-test_expect_success \"(5) push two special references (declined)\" '\n+test_expect_success \"(6) push two special references (declined)\" '\n \t(\n \t\tcd work &&\n \t\ttest_must_fail git push origin \\\n@@ -527,7 +670,7 @@ test_expect_success \"(5) push two special references (declined)\" '\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"(5) push both a normal and a special refs (declined)\" '\n+test_expect_success \"(6) push both a normal and a special refs (declined)\" '\n \t(\n \t\tcd work &&\n \t\ttest_must_fail git push origin \\\n-- \n2.26.0.rc0.5.gb02b988a14.dirty\n\n"},{"id":"392948","messageId":"20200308153816.4690-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"CANYiYbE2V3bLfEgH-aRDv4Y5V+_BTZn-oUN7fOVpARm+_14BdQ@mail.gmail.com","subject":"[PATCH v2 4/5] receive-pack: read env from proc-receive output","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-08T15:38:15Z","receivedAt":"2020-03-08T15:38:23Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThe “post-receive” hook may need the pull request ID generated by the\n“proc-receive” hook.  The results can be passed between hooks by\nenvironment variables.\n\nEach line of the message received from the standard output of the\n“proc-receive” hook in the key=value format is parsed as environment and\nthese variables will be sent to environment of the “post-receive” hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c       | 38 ++++++++++++++++++--\n t/t5411-proc-receive-hook.sh | 70 ++++++++++++++++++++++++++++++++++++\n 2 files changed, 105 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 5aff682758..d854952410 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -87,6 +87,7 @@ static const char *nonce_status;\n static long nonce_stamp_slop;\n static timestamp_t nonce_stamp_slop_limit;\n static struct ref_transaction *transaction;\n+struct argv_array post_receive_env_array;\n \n static enum {\n \tKEEPALIVE_NEVER = 0,\n@@ -694,7 +695,9 @@ struct receive_hook_feed_state {\n };\n \n typedef int (*feed_fn)(void *, const char **, size_t *);\n+typedef void (*stdout_handler_fn)(int out);\n static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n+\t\t\t     stdout_handler_fn stdout_handler,\n \t\t\t     struct receive_hook_feed_state *feed_state)\n {\n \tstruct child_process proc = CHILD_PROCESS_INIT;\n@@ -716,9 +719,15 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n \n \tproc.argv = argv;\n \tproc.in = -1;\n-\tproc.stdout_to_stderr = 1;\n+\tif (stdout_handler)\n+\t\tproc.out = -1;\n+\telse\n+\t\tproc.stdout_to_stderr = 1;\n \tproc.trace2_hook_name = hook_name;\n \n+\tif (!strcmp(hook_name, \"post-receive\") && post_receive_env_array.argc > 0)\n+\t\targv_array_pushv(&proc.env_array, post_receive_env_array.argv);\n+\n \tif (feed_state->push_options) {\n \t\tint i;\n \t\tfor (i = 0; i < feed_state->push_options->nr; i++)\n@@ -763,6 +772,10 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n \t\t\tbreak;\n \t}\n \tclose(proc.in);\n+\n+\tif (stdout_handler)\n+\t\tstdout_handler(proc.out);\n+\n \tif (use_sideband)\n \t\tfinish_async(&muxer);\n \n@@ -819,7 +832,7 @@ static int run_receive_hook(struct command *commands,\n \t\treturn 0;\n \tstate.cmd = commands;\n \tstate.push_options = push_options;\n-\tstatus = run_and_feed_hook(hook_name, feed_receive_hook, &state);\n+\tstatus = run_and_feed_hook(hook_name, feed_receive_hook, NULL, &state);\n \tstrbuf_release(&state.buf);\n \treturn status;\n }\n@@ -853,6 +866,23 @@ static int run_update_hook(struct command *cmd)\n \treturn finish_command(&proc);\n }\n \n+static void prepare_post_receive_env(int in)\n+{\n+\tstruct strbuf stdout_buf = STRBUF_INIT;\n+\n+\twhile (strbuf_getwholeline_fd(&stdout_buf, in, '\\n') != EOF) {\n+\t\tchar *p = stdout_buf.buf + stdout_buf.len -1;\n+\t\tif (*p =='\\n')\n+\t\t\t*p = '\\0';\n+\t\tp = strchr(stdout_buf.buf, '=');\n+\t\tif (p == NULL)\n+\t\t\tcontinue;\n+\t\targv_array_push(&post_receive_env_array, stdout_buf.buf);\n+\t\tstrbuf_reset(&stdout_buf);\n+\t}\n+\tstrbuf_release(&stdout_buf);\n+}\n+\n static int run_proc_receive_hook(struct command *commands,\n \t\t\t\t const struct string_list *push_options)\n {\n@@ -869,7 +899,8 @@ static int run_proc_receive_hook(struct command *commands,\n \t\treturn 0;\n \tstate.cmd = commands;\n \tstate.push_options = push_options;\n-\tstatus = run_and_feed_hook(\"proc-receive\", feed_receive_hook, &state);\n+\tstatus = run_and_feed_hook(\"proc-receive\",\n+\t\t\tfeed_receive_hook, prepare_post_receive_env, &state);\n \tstrbuf_release(&state.buf);\n \treturn status;\n }\n@@ -2040,6 +2071,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t};\n \n \tstring_list_init(&proc_receive_refs, 0);\n+\targv_array_init(&post_receive_env_array);\n \n \tpacket_trace_identity(\"receive-pack\");\n \ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex e3bb421078..6f35867e85 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -687,4 +687,74 @@ test_expect_success \"(6) push both a normal and a special refs (declined)\" '\n \ttest_cmp expect actual\n '\n \n+test_expect_success \"(7) restore pre-receive hook\" '\n+\tmv $bare/hooks/pre-receive $bare/hooks/pre-receive.fail &&\n+\tmv $bare/hooks/pre-receive.ok $bare/hooks/pre-receive\n+'\n+\n+test_expect_success \"(7) new proc-receive and post-receive hooks (pass environment variables)\" '\n+\t## proc-receive hook\n+\tmv $bare/hooks/proc-receive $bare/hooks/proc-receive.ok &&\n+\tcat >$bare/hooks/proc-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"execute: proc-receive hook\\n\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\tprintf >&2 \">> old: \\$old, new: \\$new, ref: \\$ref.\\n\"\n+\tdone\n+\n+\tprintf \"GIT_VAR1=var1\\n\"\n+\tprintf \"GIT_VAR2=var2\\n\"\n+\tprintf \"AGIT_VAR1=foo\\n\"\n+\tprintf \"AGIT_VAR2=bar\\n\"\n+\tEOF\n+\tchmod a+x $bare/hooks/proc-receive &&\n+\n+\t## post-receive hook\n+\tmv $bare/hooks/post-receive $bare/hooks/post-receive.ok &&\n+\tcat >$bare/hooks/post-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"execute: post-receive hook\\n\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\tprintf >&2 \">> old: \\$old, new: \\$new, ref: \\$ref.\\n\"\n+\tdone\n+\n+\tfor k in GIT_VAR1 GIT_VAR2 AGIT_VAR1 AGIT_VAR2\n+\tdo\n+\t\tif test -n \"\\$(eval echo \\\\\"\\\\\\$\\$k\\\")\"\n+\t\tthen\n+\t\t\tprintf >&2 \">> has env: \\$k=\\$(eval echo \\\\\"\\\\\\$\\$k\\\").\\n\"\n+\t\tfi\n+\tdone\n+\tEOF\n+\tchmod a+x $bare/hooks/post-receive\n+'\n+\n+test_expect_success \"(7) push one special ref (show environments)\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit push origin \\\n+\t\t\tHEAD:refs/for/master/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: execute: pre-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/master/my/topic.\n+\tremote: execute: proc-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/master/my/topic.\n+\tremote: execute: post-receive hook\n+\tremote: >> old: $ZERO_OID, new: $B, ref: refs/for/master/my/topic.\n+\tremote: >> has env: GIT_VAR1=var1.\n+\tremote: >> has env: GIT_VAR2=var2.\n+\tremote: >> has env: AGIT_VAR1=foo.\n+\tremote: >> has env: AGIT_VAR2=bar.\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.26.0.rc0.5.gb02b988a14.dirty\n\n"},{"id":"392947","messageId":"20200308153816.4690-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"CANYiYbE2V3bLfEgH-aRDv4Y5V+_BTZn-oUN7fOVpARm+_14BdQ@mail.gmail.com","subject":"[PATCH v2 5/5] hook: add document and example for \"proc-receive\" hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-08T15:38:16Z","receivedAt":"2020-03-08T15:38:24Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/githooks.txt           |  22 +++++\n t/t5411-proc-receive-hook.sh         |  71 ++++++++++++++++\n templates/hooks--proc-receive.sample | 119 +++++++++++++++++++++++++++\n 3 files changed, 212 insertions(+)\n create mode 100755 templates/hooks--proc-receive.sample\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 3dccab5375..1af213bad8 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -333,6 +333,28 @@ The default 'update' hook, when enabled--and with\n `hooks.allowunannotated` config option unset or set to false--prevents\n unannotated tags to be pushed.\n \n+[[proc-receive]]\n+proc-receive\n+~~~~~~~~~~~~\n+This hook is invoked by linkgit:git-receive-pack[1] when it reacts to\n+special `git push` command.  According to refnames of the commands which\n+`git push` sends to 'git-receive-pack', the commands will be devided\n+into two groups by matching what the `receive.procReceiveRefs`\n+configuration variable defines.  One group of the commands will execute\n+the internal `execute_commands` function to update the corresponding\n+refnames, and the other group of commands which have matching refnames\n+will execute this 'proc-receive' hook to create pull requests, etc.\n+If there is no `receive.procReceiveRefs` settings, this hook won't\n+execute at all, and all commands are sent to the internal\n+`execute_commands` function.\n+\n+Its exit status only determines the success or failure of the group of\n+commands with special refnames, unless atomic push is in use.\n+\n+This hook executes once for the receive operation.  It takes no\n+arguments, but gets the same information as the\n+<<pre-receive,'pre-receive'>> hook does on its standard input.\n+\n [[post-receive]]\n post-receive\n ~~~~~~~~~~~~\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex 6f35867e85..caedd49f2e 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -757,4 +757,75 @@ test_expect_success \"(7) push one special ref (show environments)\" '\n \ttest_cmp expect actual\n '\n \n+test_expect_success \"(8) install proc-receive from proc-receive.sample\" '\n+\tmv $bare/hooks/pre-receive $bare/hooks/pre-receive.ok &&\n+\tmv $bare/hooks/post-receive $bare/hooks/post-receive.env &&\n+\tmv $bare/hooks/proc-receive $bare/hooks/proc-receive.env &&\n+\tcp ../../templates/hooks--proc-receive.sample $bare/hooks/proc-receive &&\n+\tchmod a+x $bare/hooks/proc-receive\n+'\n+\n+test_expect_success \"(8) proc-receive.sample: show push result\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit push origin \\\n+\t\t\tHEAD:refs/for/a/b/c/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: [proc-receive] *******************************************************\n+\tremote: [proc-receive] * Pull request #12345678901 created/updated           *\n+\tremote: [proc-receive] * URL: https://... ...                                *\n+\tremote: [proc-receive] *******************************************************\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(8) proc-receive.sample: show debug info\" '\n+\t(\n+\t\tcd work &&\n+\t\tgit push -o debug=1 -o reviewers=user1,user2 \\\n+\t\t\torigin \\\n+\t\t\tHEAD:refs/for/a/b/c/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: [DEBUG] [proc-receive] push-option: AGIT_DEBUG=1\n+\tremote: [DEBUG] [proc-receive] push-option: AGIT_REVIEWERS=user1,user2\n+\tremote: [DEBUG] [proc-receive] command from stdin: $ZERO_OID $B refs/for/a/b/c/my/topic\n+\tremote: [DEBUG] [proc-receive] call API (AGIT_PR_TARGET=a/b/c, AGIT_PR_TOPIC=)...\n+\tremote: [DEBUG] [proc-receive] parse API result, and get AGIT_PR_ID, etc.\n+\tremote: [proc-receive] *******************************************************\n+\tremote: [proc-receive] * Pull request #12345678901 created/updated           *\n+\tremote: [proc-receive] * URL: https://... ...                                *\n+\tremote: [proc-receive] *******************************************************\n+\tremote: [DEBUG] [proc-receive] output kv pairs to stdout for git to parse.\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"(8) proc-receive.sample: fail to push non-exist branch\" '\n+\t(\n+\t\tcd work &&\n+\t\ttest_must_fail git push -o reviewers=user1,user2 \\\n+\t\t\torigin \\\n+\t\t\tHEAD:refs/for/a/b/x/my/topic\n+\t) >out 2>&1 &&\n+\tgrep \"^remote:\" out | sed -e \"s/  *\\$//g\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: [proc-receive] cannot find target branch from ref: refs/for/a/b/x/my/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd $bare &&\n+\t\tgit show-ref\n+\t) >actual &&\n+\tcat >expect <<-EOF &&\n+\t$A refs/heads/a/b/c\n+\t$A refs/heads/maint\n+\t$A refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\ndiff --git a/templates/hooks--proc-receive.sample b/templates/hooks--proc-receive.sample\nnew file mode 100755\nindex 0000000000..31fa85ffe1\n--- /dev/null\n+++ b/templates/hooks--proc-receive.sample\n@@ -0,0 +1,119 @@\n+#!/bin/sh\n+#\n+# This is an  example hook script, DO NOT use it on production service.\n+\n+debug() {\n+\tcase \"$AGIT_DEBUG\" in\n+\t\"yes\" | \"true\" | \"1\")\n+\t\t;;\n+\t*)\n+\t\treturn\n+\tesac\n+\n+\techo >&2 \"[DEBUG] $@\"\n+}\n+\n+# Parse push options\n+if test -n \"$GIT_PUSH_OPTION_COUNT\"\n+then\n+\ti=0\n+\twhile test \"$i\" -lt \"$GIT_PUSH_OPTION_COUNT\"\n+\tdo\n+\t\teval \"value=\\$GIT_PUSH_OPTION_$i\"\n+\t\ti=$((i + 1))\n+\n+\t\tk=$(echo ${value%=*} | tr [a-z] [A-Z])\n+\t\tv=${value#*=}\n+\t\tif test -n \"$v\" && test -n \"$k\"\n+\t\tthen\n+\t\t\tk=\"AGIT_$k\"\n+\t\telse\n+\t\t\tcontinue\n+\t\tfi\n+\t\teval \"$k=$v\"\n+\t\tdebug \"[proc-receive] push-option: $k=$v\"\n+\tdone\n+fi\n+\n+# Read push commands.\n+count=0\n+while read old new refname\n+do\n+\tdebug \"[proc-receive] command from stdin: $old $new $refname\"\n+\tcount=$(( count + 1 ))\n+\t# Only one special refname is allowed for each push\n+\tif test $count -gt 1\n+\tthen\n+\t\techo >&2 \"[proc-receive]: cannot handle more than one push commands\"\n+\t\texit 1\n+\tfi\n+\n+\t# Parse refname, and set envrionment\n+\tremains=\n+\tif test \"${refname#refs/for/}\" != \"$refname\"\n+\tthen\n+\t\tAGIT_PR_IS_DRAFT=false\n+\t\tremains=${refname#refs/for/}\n+\telif test \"${refname#refs/drafts/}\" != \"$refname\"\n+\tthen\n+\t\tAGIT_PR_IS_DRAFT=true\n+\t\tremains=${refname#refs/drafts/}\n+\telse\n+\t\techo >&2 \"[proc-receive] unknown refname: $refname\"\n+\t\texit 1\n+\tfi\n+\n+\tref=\n+\tfound_ref=\n+\tfor i in $(echo $remains | tr \"/\" \"\\n\")\n+\tdo\n+\t\tif test -z \"$ref\"\n+\t\tthen\n+\t\t\tref=$i\n+\t\telse\n+\t\t\tref=$ref/$i\n+\t\tfi\n+\t\tif git rev-parse --verify $ref -- 2>/dev/null\n+\t\tthen\n+\t\t\tfound_ref=yes\n+\t\t\tbreak\n+\t\tfi\n+\tdone\n+\tif test -z \"$found_ref\"\n+\tthen\n+\t\techo >&2 \"[proc-receive] cannot find target branch from ref: $refname\"\n+\t\texit 1\n+\tfi\n+\tAGIT_PR_TARGET=$ref\n+\tAGIT_PR_SOURCE=${remains#$ref/}\n+done\n+\n+if test -z \"$AGIT_PR_TARGET\"\n+then\n+\techo >&2 \"[proc-receive] fail to parse refname, no target found\"\n+\texit 1\n+fi\n+\n+# Call API to generate code review.\n+debug \"[proc-receive] call API (AGIT_PR_TARGET=$AGIT_PR_TARGET, AGIT_PR_TOPIC=$AGIT_PR_TOPIC)...\"\n+\n+# Parse result of API.\n+debug \"[proc-receive] parse API result, and get AGIT_PR_ID, etc.\"\n+AGIT_PR_ID=\"12345678901\"\n+AGIT_PR_LOCAL_ID=\"23\"\n+\n+# Show message.\n+if test -n \"$AGIT_PR_ID\"\n+then\n+\techo >&2 \"[proc-receive] *******************************************************\"\n+\techo >&2 \"[proc-receive] * Pull request #$AGIT_PR_ID created/updated           *\"\n+\techo >&2 \"[proc-receive] * URL: https://... ...                                *\"\n+\techo >&2 \"[proc-receive] *******************************************************\"\n+fi\n+\n+# Show envs to stdout, and will be exported as envs for \"post-receive\" hook.\n+debug \"[proc-receive] output kv pairs to stdout for git to parse.\"\n+echo \"AGIT_PR_ID=$AGIT_PR_ID\"\n+echo \"AGIT_PR_LOCAL_ID=$AGIT_PR_LOCAL_ID\"\n+\n+exit 0\n-- \n2.26.0.rc0.5.gb02b988a14.dirty\n\n"},{"id":"392968","messageId":"xmqq7dzt1mhi.fsf@gitster-ct.c.googlers.com","threadId":"52936","inReplyTo":"20200308145631.64040-2-zhiyou.jx@alibaba-inc.com","subject":"Re: [PATCH v2 1/5] receive-pack: add new proc-receive hook","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-03-09T17:12:09Z","receivedAt":"2020-03-09T17:12:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n>  builtin/receive-pack.c       |  93 +++++-\n>  t/t5411-proc-receive-hook.sh | 547 +++++++++++++++++++++++++++++++++++\n>  2 files changed, 628 insertions(+), 12 deletions(-)\n>  create mode 100755 t/t5411-proc-receive-hook.sh\n>\n> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\n> index 2cc18bbffd..23d0c224d2 100644\n> --- a/builtin/receive-pack.c\n> +++ b/builtin/receive-pack.c\n> @@ -312,7 +312,8 @@ struct command {\n>  \tstruct command *next;\n>  \tconst char *error_string;\n>  \tunsigned int skip_update:1,\n> -\t\t     did_not_exist:1;\n> +\t\t     did_not_exist:1,\n> +\t\t     have_special_ref:1;\n>  \tint index;\n>  \tstruct object_id old_oid;\n>  \tstruct object_id new_oid;\n> @@ -669,6 +670,9 @@ static void prepare_push_cert_sha1(struct child_process *proc)\n>  \n>  struct receive_hook_feed_state {\n>  \tstruct command *cmd;\n> +\tint feed_normal_ref;\n> +\tint feed_special_ref;\n> +\tint hook_must_exist;\n>  \tint skip_broken;\n>  \tstruct strbuf buf;\n>  \tconst struct string_list *push_options;\n> @@ -684,8 +688,14 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n>  \tint code;\n>  \n>  \targv[0] = find_hook(hook_name);\n> -\tif (!argv[0])\n> -\t\treturn 0;\n> +\tif (!argv[0]) {\n> +\t\tif (feed_state->hook_must_exist) {\n> +\t\t\trp_error(\"cannot to find hook '%s'\", hook_name);\n> +\t\t\treturn 1;\n> +\t\t} else {\n> +\t\t\treturn 0;\n> +\t\t}\n> +\t}\n>  \n>  \targv[1] = NULL;\n\nWhy do you even need \"must-exist\" in the first place?  In other\nparts of Git, when a hook does not exist, processing by that missing\nhook is simply not done.  Why does it have to cause an error?\n\nIf there is a reasonable answer to the above question, then the\nconcept that hooks can be marked as \"must exist -- otherwise it is\nan error\" would be independently useful and the support for that\nconcept should be split out as a separate step before this one, I\nwould think.\n\nAlso, I am not sure why you have to have one single \"special\" ref\nhierarchy to be handled by the hook.  Why not feed everything and\nlet the hook decide?\n\nI would have expected a design more along the lines of \"if the hook\nexists, then feed all the refs to the hook, let the hook do whatever\nit wants and report what it did back to the receive-pack process, so\nthat the client side can learn what got accepted and what got\nrejected (and possibly other things that may require protocol\nextension later)\".  One of the possible things the hook can report\nmay be \"I did not do anything for this ref, please do whatever you\nnormally do\".  That way, you do not need to introduce \"there are two\nclasses of refs, normal and special\"---that becomes an unused and\nunnecessary concept.  For whichever ref the hook considers \"normal\",\nit can use the mechanism to say \"I want you to do the normal thing\".\nAnd when it sees refs/for/ prefix (or whatever is considered\n\"special\" at the hook's discretion), it can do whatever it wants,\nand report \"OK, I ate it\" or \"No, I reject it\" (which are only the\ntwo things that can be returned back to the client within the\ncurrent protocol, IIUC).  We may later want to extend the mechanism\nto allow richer response by the hook at the same time to extend the\non-the-wire protocol and then it may become possible for the hook to\ntake a push to refs/for/master, update refs/heads/master to the\ncommit (or to some other commit), and report \"OK, I accepted it and\nalso updated refs/heads/master to this other commit\".\n"},{"id":"392983","messageId":"CANYiYbHvqLX_OozgAXJ8MbuLipqUdj4CpbExe0oiCcyUyb=Osw@mail.gmail.com","threadId":"52936","inReplyTo":"xmqq7dzt1mhi.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 1/5] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-10T06:03:22Z","receivedAt":"2020-03-10T06:03:39Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2020年3月10日周二 上午1:12写道：\n> Why do you even need \"must-exist\" in the first place?  In other\n> parts of Git, when a hook does not exist, processing by that missing\n> hook is simply not done.  Why does it have to cause an error?\n>\n> If there is a reasonable answer to the above question, then the\n> concept that hooks can be marked as \"must exist -- otherwise it is\n> an error\" would be independently useful and the support for that\n> concept should be split out as a separate step before this one, I\n> would think.\n>\n> Also, I am not sure why you have to have one single \"special\" ref\n> hierarchy to be handled by the hook.  Why not feed everything and\n> let the hook decide?\n>\n> I would have expected a design more along the lines of \"if the hook\n> exists, then feed all the refs to the hook, let the hook do whatever\n> it wants and report what it did back to the receive-pack process, so\n> that the client side can learn what got accepted and what got\n> rejected (and possibly other things that may require protocol\n> extension later)\".  One of the possible things the hook can report\n> may be \"I did not do anything for this ref, please do whatever you\n> normally do\".  That way, you do not need to introduce \"there are two\n> classes of refs, normal and special\"---that becomes an unused and\n> unnecessary concept.  For whichever ref the hook considers \"normal\",\n> it can use the mechanism to say \"I want you to do the normal thing\".\n> And when it sees refs/for/ prefix (or whatever is considered\n> \"special\" at the hook's discretion), it can do whatever it wants,\n> and report \"OK, I ate it\" or \"No, I reject it\" (which are only the\n> two things that can be returned back to the client within the\n> current protocol, IIUC).  We may later want to extend the mechanism\n> to allow richer response by the hook at the same time to extend the\n> on-the-wire protocol and then it may become possible for the hook to\n> take a push to refs/for/master, update refs/heads/master to the\n> commit (or to some other commit), and report \"OK, I accepted it and\n> also updated refs/heads/master to this other commit\".\n\nIn patch 3/5, I add a new config variable \"receive.procReceiveRefs\"\nfor developers to choose whether all of the commands or only a part of\nthem should pipe to the \"proc-receive\" hook. For example, a developer\nmay set the config variable on the server like this:\n\n    git config --system --add receive.procReceiveRefs refs/\n\nThen all commands will pipe to the \"proc-receive\" hook.\n\nThink there may be thousands of commands in the initial stage for\nsetting up a new repository, and all of the commands are \"normal\"\n(creating branches or tags). If the \"proc-receive\" hook has to handle\nall of the \"normal\" commands by piping these commands to a \"git\nupdate-ref --stdin\" process, or pass these commands back to\n\"receive-pack\" by a new protocol, this will greatly impact the\nperformance. I think it's better to let the developer to choose by\nusing a config variable like \"receive.procReceiveRefs\".\n\nPatch 4/5 (receive-pack: read env from proc-receive output) is\nunnecessary, and extend the on-the-wire protocol is a good idea. The\nprotocol may using pkt-line format, and negotiation between\nreceive-pack (S) and the hook (H) like this:\n\n    S: PKT-LINE(version=1\\0side-band-64k ...)  # I'm not sure it is\nnecessary for the capabilities for all controllable server side\nsoftwares.\n    S: flush-pkt\n    H: PKT-LINE(version=1\\0side-band-64k ...)\n    H: flush-pkt\n    S: PKT-LINE(old-oid new-oid ref)\n    S: ... ...\n    S: flush-pkt\n    S: PKT-LINE(push-option)\n    S: ... ...\n    S: flush-pkt\n    H: PKT-LINE( sideband< progress... > )\n    H: ... ...\n    H: PKT-LINE(old-oid new-oid ref OK) # OK, I ate it\n    H: PKT-LINE(old-oid new-oid ref error-msg...) # NO, I reject it\n    H: PKT-LINE(old-oid new-oid ref)  # Back to receive-pack to handle it\n    H: ... ...\n    H: flush-pkt\n\nAfter piping the \"special\" commands to the \"proc-receive\" hook,\nreceive-pack should discard the \"special\" commands, and add the\ncommands back from the output of the \"proc-receive\" hook. Then\n\"receive-pack\" will continue to execute the internal\n\"execute_commands\" function for the unexecuted commands.\n\nIn order to have performance similar to the vanilla receive-pack, it\nis necessary to keep the config variable and the concept of \"normal\"\nand \"special\" references. If the config variable\n\"receive.procReceiveRefs\" is defined on the server, but the\n\"proc-receive\" hook does not exist, it's better give an error instead\nof fall back to the internal \"execute_commands\" function or doing\nnothing.\n\n--\nJiang Xin\n"},{"id":"393200","messageId":"20200313122318.78000-1-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"CANYiYbHvqLX_OozgAXJ8MbuLipqUdj4CpbExe0oiCcyUyb=Osw@mail.gmail.com","subject":"[PATCH v3 0/4] New proc-receive hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-13T12:23:14Z","receivedAt":"2020-03-13T12:23:39Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Before writing test cases, I want to make sure whether it is on the\nright direction.\n\nChanges since v2:\n\n* Implement a on-the-wire protocol, see commit 1/4.\n\n* Refactor report() on both server and client side for reporting of\n  proc-receive.\n\n--\n\nJiang Xin (4):\n  receive-pack: add new proc-receive hook\n  receive-pack: refactor report for proc-receive\n  refs.c: refactor to reuse ref_is_hidden()\n  receive-pack: new config receive.procReceiveRefs\n\n Documentation/config/receive.txt |  14 ++\n builtin/receive-pack.c           | 294 ++++++++++++++++++++++++++++++-\n refs.c                           |  11 +-\n refs.h                           |   1 +\n transport.c                      |  33 +++-\n 5 files changed, 332 insertions(+), 21 deletions(-)\n\n-- \n2.26.0.rc1.5.gca1e965b06\n\n"},{"id":"393201","messageId":"20200313122318.78000-2-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"CANYiYbHvqLX_OozgAXJ8MbuLipqUdj4CpbExe0oiCcyUyb=Osw@mail.gmail.com","subject":"[PATCH v3 1/4] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-13T12:23:15Z","receivedAt":"2020-03-13T12:23:42Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Git calls an internal `execute_commands` function to handle commands\nsent from client to `git-receive-pack`.  Regardless of what references\nthe user pushes, git creates or updates the corresponding references if\nthe user has write-permission.  A contributor who has no\nwrite-permission, cannot push to the repository directly.  So, the\ncontributor has to write commits to an alternate location, and sends\npull request by emails or by other ways.  We call this workflow as a\ndistributed workflow.\n\nIt would be more convenient to work in a centralized workflow like what\nGerrit provided for some cases.  For example, a read-only user who\ncannot push to a branch directly can run the following `git push`\ncommand to push commits to a pseudo reference (has a prefix \"refs/for/\",\nnot \"refs/heads/\") to create a code review.\n\n    git push origin \\\n        HEAD:refs/for/<branch-name>/<session>\n\nThe `<branch-name>` in the above example can be as simple as \"master\",\nor a more complicated branch name like \"foo/bar\".  The `<session>` in\nthe above example command can be the local branch name of the client\nside, such as \"my/topic\".\n\nWe cannot implement a centralized workflow elegantly by using\n\"pre-receive\" + \"post-receive\", because Git will call the internal\nfunction \"execute_commands\" to create references (even the special\npseudo reference) between these two hooks.  Even though we can delete\nthe temporarily created pseudo reference via the \"post-receive\" hook,\nhaving a temporary reference is not safe for concurrent pushes.\n\nSo, add a filter and a new handler to support this kind of workflow.\nThe filter will check the prefix of the reference name, and if the\ncommand has a special reference name, the filter will turn a specific\nfield (`have_special_ref`) on for the command.  Commands with this filed\nturned on will be executed by a new handler (an hook named\n\"proc-receive\") instead of the internal `execute_commands` function.\nWe can use this \"proc-receive\" command to create pull requests or send\nemails for code review.\n\nThis \"proc-receive\" hook reads commands, push-options (optional), and\nsend result using a protocol in pkt-line format.  In the following\nexample, The letter \"S\" stands for \"receive-pack\" and letter \"H\" stands\nfor the hook.\n\n    S: PKT-LINE(version=1\\0push-options ...)\n    S: flush-pkt\n\n    H: PKT-LINE(version=1\\0push-options ...)\n    H: flush-pkt\n\n    S: PKT-LINE(old-oid new-oid ref)\n    S: ... ...\n    S: flush-pkt\n\n    # Optional, only if push-options is negotiated.\n    S: PKT-LINE(push-option)\n    S: ... ...\n    S: flush-pkt\n\n    # OK, run this command successfully.\n    H: PKT-LINE(old-oid new-oid ref ok)\n\n    # NO, I reject it.\n    H: PKT-LINE(old-oid new-oid ref ng reason)\n\n    # OK, but use an alternate reference. (in latter commit)\n    H: PKT-LINE(old-oid new-oid ref ok ref:alt-ref)\n\n    # It will fallthrough to receive-pack to execute. (in latter commit)\n    H: PKT-LINE(old-oid new-oid ref ft)\n\n    H: ... ...\n    H: flush-pkt\n\nAfter receiving a command, the hook can create/update another alternate\nreference.  For example, a command for a reference \"refs/for/master\" may\ncreate a special reference, such as \"refs/pull/123/head\".  The alternate\nreference can be returned from the result in an extensible format like\n\"<old-oid> <new-oid> <reference> <status> [<message>]\".\n\nThe result will be stored in a command list, and \"receive-pack\" will use\nthe result to replace the commands that have specific `have_special_ref`\nfield turned on.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c | 244 ++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 239 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 2cc18bbffd..8cda2b9cf7 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -312,7 +312,8 @@ struct command {\n \tstruct command *next;\n \tconst char *error_string;\n \tunsigned int skip_update:1,\n-\t\t     did_not_exist:1;\n+\t\t     did_not_exist:1,\n+\t\t     have_special_ref:1;\n \tint index;\n \tstruct object_id old_oid;\n \tstruct object_id new_oid;\n@@ -817,6 +818,209 @@ static int run_update_hook(struct command *cmd)\n \treturn finish_command(&proc);\n }\n \n+static int read_proc_receive_result(struct packet_reader *reader,\n+\t\t\t\t    struct command **commands)\n+{\n+\tstruct command **tail = commands;\n+\tint code = 0;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tstruct command *cmd;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\t\tchar *status;\n+\t\tchar *msg = NULL;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ')\n+\t\t\tdie(\"protocol error: proc-receive expected 'old new ref status [msg]', got '%s'\",\n+\t\t\t    reader->line);\n+\n+\t\trefname = p;\n+\t\tstatus = strchr(p, ' ');\n+\t\tif (!status)\n+\t\t\tdie(\"protocol error: proc-receive expected 'old new ref status [msg]', got '%s'\",\n+\t\t\t    reader->line);\n+\t\t*status++ = '\\0';\n+\n+\t\tFLEX_ALLOC_MEM(cmd, ref_name, refname, strlen(refname));\n+\t\toidcpy(&cmd->old_oid, &old_oid);\n+\t\toidcpy(&cmd->new_oid, &new_oid);\n+\t\tcmd->have_special_ref = 1;\n+\n+\t\tif (strlen(status) > 2 && *(status + 2) == ' ') {\n+\t\t\tmsg = status + 2;\n+\t\t\t*msg++ = '\\0';\n+\t\t}\n+\t\tif (strlen(status) != 2)\n+\t\t\tdie(\"protocol error: proc-receive has bad status '%s' for '%s'\",\n+\t\t\t    status, reader->line);\n+\t\tif (!strcmp(status, \"ng\")) {\n+\t\t\tif (msg)\n+\t\t\t\tcmd->error_string = xstrdup(msg);\n+\t\t\telse\n+\t\t\t\tcmd->error_string = \"failed\";\n+\t\t\tcode = 1;\n+\t\t} else if (strcmp(\"ok\", status)) {\n+\t\t\trp_warning(\"unknown proc-receive status '%s' for '%s'\",\n+\t\t\t\t   status, reader->line);\n+\t\t\tcmd->error_string = \"bad status\";\n+\t\t\tcode = 1;\n+\t\t}\n+\n+\t\t*tail = cmd;\n+\t\ttail = &cmd->next;\n+\t}\n+\treturn code;\n+}\n+\n+static int run_proc_receive_hook(struct command **commands,\n+\t\t\t\t const struct string_list *push_options)\n+{\n+\tstruct child_process proc = CHILD_PROCESS_INIT;\n+\tstruct async muxer;\n+\tstruct command *result_commands = NULL;\n+\tstruct command *cmd;\n+\tconst char *argv[2];\n+\tstruct packet_reader reader;\n+\tint pr_use_push_options = 0;\n+\tint version = 0;\n+\tint code;\n+\n+\targv[0] = find_hook(\"proc-receive\");\n+\tif (!argv[0]) {\n+\t\trp_error(\"cannot to find hook 'proc-receive'\");\n+\t\treturn 1;\n+\t}\n+\targv[1] = NULL;\n+\n+\tproc.argv = argv;\n+\tproc.in = -1;\n+\tproc.out = -1;\n+\tproc.trace2_hook_name = \"proc-receive\";\n+\n+\tif (use_sideband) {\n+\t\tmemset(&muxer, 0, sizeof(muxer));\n+\t\tmuxer.proc = copy_to_sideband;\n+\t\tmuxer.in = -1;\n+\t\tcode = start_async(&muxer);\n+\t\tif (code)\n+\t\t\treturn code;\n+\t\tproc.err = muxer.in;\n+\t} else {\n+\t\tproc.err = 0;\n+\t}\n+\n+\tcode = start_command(&proc);\n+\tif (code) {\n+\t\tif (use_sideband)\n+\t\t\tfinish_async(&muxer);\n+\t\treturn code;\n+\t}\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\n+\t/* Version negotiaton */\n+\tpacket_reader_init(&reader, proc.out, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\tpacket_write_fmt(proc.in, \"version=1%c%s\\n\",\n+\t\t\t '\\0',\n+\t\t\t use_push_options ? \"push-options\": \"\");\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader.pktlen > 8 && starts_with(reader.line, \"version=\")) {\n+\t\t\tversion = atoi(reader.line+8);\n+\t\t\tlinelen = strlen(reader.line);\n+\t\t\tif (linelen < reader.pktlen) {\n+\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tpr_use_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (version != 1)\n+\t\tdie(\"protocol error: unknown proc-receive version '%d'\", version);\n+\n+\t/* Send commands */\n+\tfor (cmd = *commands; cmd; cmd = cmd->next) {\n+\t\tchar *old_hex, *new_hex;\n+\n+\t\tif (!cmd->have_special_ref || cmd->skip_update ||\n+\t\t    cmd->did_not_exist || cmd->error_string)\n+\t\t\tcontinue;\n+\n+\t\told_hex = oid_to_hex(&cmd->old_oid);\n+\t\tnew_hex = oid_to_hex(&cmd->new_oid);\n+\n+\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n+\t\t\t\t old_hex, new_hex, cmd->ref_name);\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\t/* Send push options */\n+\tif (pr_use_push_options) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tfor_each_string_list_item(item, push_options)\n+\t\t\tpacket_write_fmt(proc.in, \"%s\", item->string);\n+\n+\t\tpacket_flush(proc.in);\n+\t}\n+\n+\t/* Read result from proc-receive */\n+\tcode = read_proc_receive_result(&reader, &result_commands);\n+\tclose(proc.in);\n+\tclose(proc.out);\n+\tif (finish_command(&proc))\n+\t\tdie(\"proc-receive did not exit properly\");\n+\n+\t/* After receiving the result from the \"proc-receive\" hook,\n+\t * \"receive-pack\" will use the result to replace commands that\n+\t * have specific `have_special_ref` field.\n+\t */\n+\tfor (cmd = *commands; cmd; cmd = cmd->next)\n+\t\tif (!cmd->have_special_ref)\n+\t\t\tbreak;\n+\n+\tif (!cmd || cmd->have_special_ref) {\n+\t\tcmd = result_commands;\n+\t} else {\n+\t\tstruct command *tail = cmd;\n+\t\tfor (;;) {\n+\t\t\tif (!tail->next)\n+\t\t\t       break;\n+\t\t\telse if (tail->next->have_special_ref)\n+\t\t\t\ttail->next = tail->next->next;\n+\t\t\telse\n+\t\t\t\ttail = tail->next;\n+\t\t}\n+\t\ttail->next = result_commands;\n+\t}\n+\t*commands = cmd;\n+\n+\t// TODO: sort commands or not?\n+\n+\tif (use_sideband)\n+\t\tfinish_async(&muxer);\n+\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn code;\n+}\n+\n static char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n@@ -1392,7 +1596,7 @@ static void execute_commands_non_atomic(struct command *commands,\n \tstruct strbuf err = STRBUF_INIT;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->have_special_ref)\n \t\t\tcontinue;\n \n \t\ttransaction = ref_transaction_begin(&err);\n@@ -1432,7 +1636,7 @@ static void execute_commands_atomic(struct command *commands,\n \t}\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->have_special_ref)\n \t\t\tcontinue;\n \n \t\tcmd->error_string = update(cmd, si);\n@@ -1458,16 +1662,19 @@ static void execute_commands_atomic(struct command *commands,\n \tstrbuf_release(&err);\n }\n \n-static void execute_commands(struct command *commands,\n+static void execute_commands(struct command **orig_commands,\n \t\t\t     const char *unpacker_error,\n \t\t\t     struct shallow_info *si,\n \t\t\t     const struct string_list *push_options)\n {\n+\tstruct command *commands = *orig_commands;\n \tstruct check_connected_options opt = CHECK_CONNECTED_INIT;\n \tstruct command *cmd;\n \tstruct iterate_data data;\n \tstruct async muxer;\n \tint err_fd = 0;\n+\tint have_special_ref = 0;\n+\tint have_normal_ref = 0;\n \n \tif (unpacker_error) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n@@ -1497,6 +1704,22 @@ static void execute_commands(struct command *commands,\n \n \treject_updates_to_hidden(commands);\n \n+\t/* Try to find commands that have special prefix in their reference names,\n+\t * and mark them to run an external \"proc-receive\" hook later.\n+\t */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!should_process_cmd(cmd))\n+\t\t\tcontinue;\n+\n+\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n+\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n+\t\t\tcmd->have_special_ref = 1;\n+\t\t\thave_special_ref = 1;\n+\t\t} else {\n+\t\t\thave_normal_ref = 1;\n+\t\t}\n+\t}\n+\n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n \t\t\tif (!cmd->error_string)\n@@ -1523,6 +1746,17 @@ static void execute_commands(struct command *commands,\n \tfree(head_name_to_free);\n \thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, NULL, NULL);\n \n+\tif (have_special_ref) {\n+\t\tif (run_proc_receive_hook(orig_commands, push_options)) {\n+\t\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\t\tif (!cmd->error_string  && (cmd->have_special_ref || use_atomic))\n+\t\t\t\t\tcmd->error_string = \"fail to run proc-receive hook\";\n+\t\t\t}\n+\t\t} else {\n+\t\t\tcommands = *orig_commands;\n+\t\t}\n+\t}\n+\n \tif (use_atomic)\n \t\texecute_commands_atomic(commands, si);\n \telse\n@@ -2019,7 +2253,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\t\tupdate_shallow_info(commands, &si, &ref);\n \t\t}\n \t\tuse_keepalive = KEEPALIVE_ALWAYS;\n-\t\texecute_commands(commands, unpack_status, &si,\n+\t\texecute_commands(&commands, unpack_status, &si,\n \t\t\t\t &push_options);\n \t\tif (pack_lockfile)\n \t\t\tunlink_or_warn(pack_lockfile);\n-- \n2.26.0.rc1.5.gca1e965b06\n\n"},{"id":"393202","messageId":"20200313122318.78000-3-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"CANYiYbHvqLX_OozgAXJ8MbuLipqUdj4CpbExe0oiCcyUyb=Osw@mail.gmail.com","subject":"[PATCH v3 2/4] receive-pack: refactor report for proc-receive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-13T12:23:16Z","receivedAt":"2020-03-13T12:23:45Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"The \"proc-receive\" may update one or more references, and will send its\nresult one by one in pkt-line format.  Each line of the result has four\nfields and one optional message field, as \"<old-oid> <new-oid> <ref>\n<status> [<message>]\".  See the following example:\n\n    # OK, run this command successfully.\n    PKT-LINE(old-oid new-oid ref ok)\n\n    # NO, I reject it.\n    PKT-LINE(old-oid new-oid ref ng reason)\n\n    # OK, but use an alternate reference.\n    PKT-LINE(old-oid new-oid ref ok ref:alt-ref)\n\n    # It will fallthrough to receive-pack to execute.\n    PKT-LINE(old-oid new-oid ref ft)\n\nThe first three fields have the same foramt as a command.\n\nThe forth field has a two-letter status code.  Available status code:\n\n* ok: The command runs successfully.  If the optional message has a\n  prefix \"ref:\", the hook has created/updated an alternate reference\n  instead.\n\n* ng: Fail to run the command. Error message is in the optional message\n  field.\n\n* ft: Will fallthrough to receive-pack to execute.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c | 21 ++++++++++++++++-----\n transport.c            | 33 ++++++++++++++++++++++++---------\n 2 files changed, 40 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 8cda2b9cf7..23f1ae3795 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -311,6 +311,7 @@ static void write_head_info(void)\n struct command {\n \tstruct command *next;\n \tconst char *error_string;\n+\tconst char *extra_string;\n \tunsigned int skip_update:1,\n \t\t     did_not_exist:1,\n \t\t     have_special_ref:1;\n@@ -868,7 +869,12 @@ static int read_proc_receive_result(struct packet_reader *reader,\n \t\t\telse\n \t\t\t\tcmd->error_string = \"failed\";\n \t\t\tcode = 1;\n-\t\t} else if (strcmp(\"ok\", status)) {\n+\t\t} else if (!strcmp(\"ok\", status)) {\n+\t\t\tcmd->extra_string = xstrdup_or_null(msg);\n+\t\t} else if (!strcmp(\"ft\", status)) {\n+\t\t\t/* Unset \"have_special_ref\" field, will execute by \"receive-pack\" */\n+\t\t\tcmd->have_special_ref = 0;\n+\t\t} else {\n \t\t\trp_warning(\"unknown proc-receive status '%s' for '%s'\",\n \t\t\t\t   status, reader->line);\n \t\t\tcmd->error_string = \"bad status\";\n@@ -2133,12 +2139,17 @@ static void report(struct command *commands, const char *unpack_status)\n \tpacket_buf_write(&buf, \"unpack %s\\n\",\n \t\t\t unpack_status ? unpack_status : \"ok\");\n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!cmd->error_string)\n-\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n-\t\t\t\t\t cmd->ref_name);\n-\t\telse\n+\t\tif (!cmd->error_string) {\n+\t\t\tif (!cmd->extra_string)\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n+\t\t\t\t\t\t cmd->ref_name);\n+\t\t\telse\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s%c%s\\n\",\n+\t\t\t\t\t\t cmd->ref_name, ' ', cmd->extra_string);\n+\t\t} else {\n \t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n \t\t\t\t\t cmd->ref_name, cmd->error_string);\n+\t\t}\n \t}\n \tpacket_buf_flush(&buf);\n \ndiff --git a/transport.c b/transport.c\nindex 1fdc7dac1a..a4f2a5cc6f 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -463,11 +463,17 @@ static void print_ref_status(char flag, const char *summary,\n \t\t\t     struct ref *to, struct ref *from, const char *msg,\n \t\t\t     int porcelain, int summary_width)\n {\n+\tchar *to_name = to->name;\n+\n+\tif (to->remote_status) {\n+\t\tif (!strncmp(\"ref:\", to->remote_status, 4))\n+\t\t\tto_name = to->remote_status + 4;\n+\t}\n \tif (porcelain) {\n \t\tif (from)\n-\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to_name);\n \t\telse\n-\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to_name);\n \t\tif (msg)\n \t\t\tfprintf(stdout, \"%s (%s)\\n\", summary, msg);\n \t\telse\n@@ -481,9 +487,9 @@ static void print_ref_status(char flag, const char *summary,\n \t\tfprintf(stderr, \" %s%c %-*s%s \", red, flag, summary_width,\n \t\t\tsummary, reset);\n \t\tif (from)\n-\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(to->name));\n+\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(to_name));\n \t\telse\n-\t\t\tfputs(prettify_refname(to->name), stderr);\n+\t\t\tfputs(prettify_refname(to_name), stderr);\n \t\tif (msg) {\n \t\t\tfputs(\" (\", stderr);\n \t\t\tfputs(msg, stderr);\n@@ -498,12 +504,21 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \tif (ref->deletion)\n \t\tprint_ref_status('-', \"[deleted]\", ref, NULL, NULL,\n \t\t\t\t porcelain, summary_width);\n-\telse if (is_null_oid(&ref->old_oid))\n+\telse if (is_null_oid(&ref->old_oid)) {\n+\t\tchar *refname, *summary;\n+\t\tif (ref->remote_status && !strncmp(ref->remote_status, \"ref:\", 4))\n+\t\t\trefname = ref->remote_status + 4;\n+\t\telse\n+\t\t\trefname = ref->name;\n+\t\tif (starts_with(refname, \"refs/tags/\"))\n+\t\t       summary = \"[new tag]\";\n+\t\telse if (starts_with(refname, \"refs/heads/\"))\n+\t\t       summary = \"[new branch]\";\n+\t\telse\n+\t\t       summary = \"[new reference]\";\n \t\tprint_ref_status('*',\n-\t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n-\t\t\t\"[new branch]\"),\n-\t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n-\telse {\n+\t\t\tsummary, ref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t} else {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n \t\tconst char *msg;\n-- \n2.26.0.rc1.5.gca1e965b06\n\n"},{"id":"393203","messageId":"20200313122318.78000-5-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"CANYiYbHvqLX_OozgAXJ8MbuLipqUdj4CpbExe0oiCcyUyb=Osw@mail.gmail.com","subject":"[PATCH v3 4/4] receive-pack: new config receive.procReceiveRefs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-13T12:23:18Z","receivedAt":"2020-03-13T12:23:48Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Add a new multi-valued config variable \"receive.procReceiveRefs\"\nfor `receive-pack` command, like the follows:\n\n    git config --system --add receive.procReceiveRefs refs/for/\n    git config --system --add receive.procReceiveRefs refs/drafts/\n\nIf the specific prefix strings match the reference names of the commands\nwhich are sent by git client to `receive-pack`, these commands will be\nexecuted by an external hook (named \"proc-receive\"), instead of the\ninternal `execute_commands` function.\n\nFor example, if it is set to \"refs/for/\", pushing to a reference such as\n\"refs/for/master\" will not create or update reference \"refs/for/master\",\nbut may create or update a pull request directly by running the external\nhook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/config/receive.txt | 14 +++++++++\n builtin/receive-pack.c           | 49 ++++++++++++++++++++++++++------\n 2 files changed, 54 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/config/receive.txt b/Documentation/config/receive.txt\nindex 65f78aac37..0178f2d478 100644\n--- a/Documentation/config/receive.txt\n+++ b/Documentation/config/receive.txt\n@@ -114,6 +114,20 @@ receive.hideRefs::\n \tAn attempt to update or delete a hidden ref by `git push` is\n \trejected.\n \n+receive.procReceiveRefs::\n+\tThis is a multi-valued variable that defines reference prefixes\n+\tto match the commands in `receive-pack`.  Commands matching the\n+\tprefixes will be executed by an external hooks \"proc-receive\",\n+\tinstead of the internal `execute_commands` function.  If this\n+\tvariable is not defined, the \"proc-receive\" hook will never be\n+\tused, and all commands will be executed by the internal\n+\t`execute_commands` function.\n+\n+\tFor example, if this variable is set to \"refs/for/\", pushing to\n+\treference such as \"refs/for/master\" will not create or update a\n+\treference named \"refs/for/master\", but may create or update a\n+\tpull request directly by running an external hook.\n+\n receive.updateServerInfo::\n \tIf set to true, git-receive-pack will run git-update-server-info\n \tafter receiving data from git-push and updating refs.\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 23f1ae3795..2b796e654d 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -76,6 +76,7 @@ static struct object_id push_cert_oid;\n static struct signature_check sigcheck;\n static const char *push_cert_nonce;\n static const char *cert_nonce_seed;\n+static struct string_list proc_receive_refs;\n \n static const char *NONCE_UNSOLICITED = \"UNSOLICITED\";\n static const char *NONCE_BAD = \"BAD\";\n@@ -228,6 +229,20 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (strcmp(var, \"receive.procreceiverefs\") == 0) {\n+\t\tchar *prefix;\n+\t\tint len;\n+\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tprefix = xstrdup(value);\n+\t\tlen = strlen(prefix);\n+\t\twhile (len && prefix[len - 1] == '/')\n+\t\t\tprefix[--len] = '\\0';\n+\t\tstring_list_insert(&proc_receive_refs, prefix);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -1713,17 +1728,30 @@ static void execute_commands(struct command **orig_commands,\n \t/* Try to find commands that have special prefix in their reference names,\n \t * and mark them to run an external \"proc-receive\" hook later.\n \t */\n-\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n-\t\t\tcontinue;\n+\tif (proc_receive_refs.nr > 0) {\n+\t\tstruct strbuf refname_full = STRBUF_INIT;\n+\t\tsize_t prefix_len;\n \n-\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n-\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n-\t\t\tcmd->have_special_ref = 1;\n-\t\t\thave_special_ref = 1;\n-\t\t} else {\n-\t\t\thave_normal_ref = 1;\n+\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n+\t\tprefix_len = refname_full.len;\n+\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tif (!should_process_cmd(cmd))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_setlen(&refname_full, prefix_len);\n+\t\t\tstrbuf_addstr(&refname_full, cmd->ref_name);\n+\t\t\tif (ref_is_matched(&proc_receive_refs, cmd->ref_name, refname_full.buf)) {\n+\t\t\t\tcmd->have_special_ref = 1;\n+\t\t\t\thave_special_ref = 1;\n+\t\t\t} else {\n+\t\t\t\thave_normal_ref = 1;\n+\t\t\t}\n \t\t}\n+\n+\t\tstrbuf_release(&refname_full);\n+\t} else {\n+\t\thave_normal_ref = 1;\n \t}\n \n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n@@ -2187,6 +2215,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n+\tstring_list_init(&proc_receive_refs, 0);\n+\n \tpacket_trace_identity(\"receive-pack\");\n \n \targc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);\n@@ -2302,5 +2332,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \toid_array_clear(&shallow);\n \toid_array_clear(&ref);\n \tfree((void *)push_cert_nonce);\n+\tstring_list_clear(&proc_receive_refs, 0);\n \treturn 0;\n }\n-- \n2.26.0.rc1.5.gca1e965b06\n\n"},{"id":"393204","messageId":"20200313122318.78000-4-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"CANYiYbHvqLX_OozgAXJ8MbuLipqUdj4CpbExe0oiCcyUyb=Osw@mail.gmail.com","subject":"[PATCH v3 3/4] refs.c: refactor to reuse ref_is_hidden()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-13T12:23:17Z","receivedAt":"2020-03-13T12:23:49Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Add new function `ref_is_matched()` to reuse `ref_is_hidden()`. Will use\nthis function for `receive-pack` to check commands with specific\nprefixes.\n\nTest case t5512 covered this change.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n refs.c | 11 ++++++++---\n refs.h |  1 +\n 2 files changed, 9 insertions(+), 3 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 1ab0bb54d3..229159ea1a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1389,13 +1389,18 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti\n }\n \n int ref_is_hidden(const char *refname, const char *refname_full)\n+{\n+\treturn ref_is_matched(hide_refs, refname, refname_full);\n+}\n+\n+int ref_is_matched(struct string_list *match_refs, const char *refname, const char *refname_full)\n {\n \tint i;\n \n-\tif (!hide_refs)\n+\tif (!match_refs)\n \t\treturn 0;\n-\tfor (i = hide_refs->nr - 1; i >= 0; i--) {\n-\t\tconst char *match = hide_refs->items[i].string;\n+\tfor (i = match_refs->nr - 1; i >= 0; i--) {\n+\t\tconst char *match = match_refs->items[i].string;\n \t\tconst char *subject;\n \t\tint neg = 0;\n \t\tconst char *p;\ndiff --git a/refs.h b/refs.h\nindex 545029c6d8..a2ea043f7f 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -739,6 +739,7 @@ int parse_hide_refs_config(const char *var, const char *value, const char *);\n  * parameter always points to the full ref name.\n  */\n int ref_is_hidden(const char *, const char *);\n+int ref_is_matched(struct string_list *, const char *, const char *);\n \n enum ref_type {\n \tREF_TYPE_PER_WORKTREE,\t  /* refs inside refs/ but not shared       */\n-- \n2.26.0.rc1.5.gca1e965b06\n\n"},{"id":"393706","messageId":"20200322131815.11872-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200313122318.78000-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v4 0/5] New proc-receive hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-22T13:18:10Z","receivedAt":"2020-03-22T13:18:32Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Changes since v2 and v3:\n\n* Implement a on-the-wire protocol, and add new test helper to test\n  the on-the-wire protocol. See patch 2/5.\n\n* Refactor report() on both server and client side for reporting of\n  proc-receive. See patch 5/5.\n\n\nJiang Xin (5):\n  transport: not report a non-head push as a branch\n  receive-pack: add new proc-receive hook\n  refs.c: refactor to reuse ref_is_hidden()\n  receive-pack: new config receive.procReceiveRefs\n  receive-pack: refactor report for proc-receive\n\n Documentation/config/receive.txt |  14 +\n Makefile                         |   1 +\n builtin/receive-pack.c           | 323 ++++++++++-\n refs.c                           |  11 +-\n refs.h                           |   1 +\n t/helper/test-proc-receive.c     | 164 ++++++\n t/helper/test-tool.c             |   1 +\n t/helper/test-tool.h             |   1 +\n t/t5411-proc-receive-hook.sh     | 954 +++++++++++++++++++++++++++++++\n transport.c                      |  28 +-\n 10 files changed, 1478 insertions(+), 20 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100755 t/t5411-proc-receive-hook.sh\n\n-- \n2.26.0.rc1.33.g4914ba4bcf\n\n"},{"id":"393707","messageId":"20200322131815.11872-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200313122318.78000-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v4 1/5] transport: not report a non-head push as a branch","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-22T13:18:11Z","receivedAt":"2020-03-22T13:18:32Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing a new reference (not a head or tag), report it as a new\nreference instead of a new branch.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411-proc-receive-hook.sh | 144 +++++++++++++++++++++++++++++++++++\n transport.c                  |   3 +-\n 2 files changed, 146 insertions(+), 1 deletion(-)\n create mode 100755 t/t5411-proc-receive-hook.sh\n\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nnew file mode 100755\nindex 0000000000..fe2861f2e6\n--- /dev/null\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -0,0 +1,144 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook'\n+\n+. ./test-lib.sh\n+\n+# Create commits in <repo> and assign each commit's oid to shell variables\n+# given in the arguments (A, B, and C). E.g.:\n+#\n+#     create_commits_in <repo> A B C\n+#\n+# NOTE: Avoid calling this function from a subshell since variable\n+# assignments will disappear when subshell exits.\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{} 2>/dev/null)\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+format_git_output () {\n+\tsed \\\n+\t\t-e \"s/  *\\$//g\" \\\n+\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n+\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n+\t\t-e \"s/$TAG/<COMMIT-T>/g\" \\\n+\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n+\t\t-e \"s/'/\\\"/g\"\n+}\n+\n+test_expect_success \"setup\" '\n+\tgit init --bare upstream &&\n+\tgit init workbench &&\n+\tcreate_commits_in workbench A B &&\n+\t(\n+\t\tcd workbench &&\n+\t\tgit remote add origin ../upstream &&\n+\t\tgit config core.abbrev 7 &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit tag -m \"v1.0.0\" v1.0.0 $A &&\n+\t\tgit push origin \\\n+\t\t\t$B:refs/heads/master \\\n+\t\t\t$A:refs/heads/next\n+\t) &&\n+\tTAG=$(cd workbench; git rev-parse v1.0.0) &&\n+\n+\t# setup pre-receive hook\n+\tcat >upstream/hooks/pre-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# pre-receive hook\\n\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\tprintf >&2 \"pre-receive< \\$old \\$new \\$ref\\n\"\n+\tdone\n+\tEOF\n+\n+\t# setup post-receive hook\n+\tcat >upstream/hooks/post-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# post-receive hook\\n\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\tprintf >&2 \"post-receive< \\$old \\$new \\$ref\\n\"\n+\tdone\n+\tEOF\n+\n+\tchmod a+x \\\n+\t\tupstream/hooks/pre-receive \\\n+\t\tupstream/hooks/post-receive\n+'\n+\n+test_expect_success \"normal git-push command\" '\n+\t(\n+\t\tcd workbench &&\n+\t\tgit push -f origin \\\n+\t\t\trefs/tags/v1.0.0 \\\n+\t\t\t:refs/heads/next \\\n+\t\t\tHEAD:refs/heads/master \\\n+\t\t\tHEAD:refs/review/master/topic \\\n+\t\t\tHEAD:refs/heads/a/b/c\n+\t) >out 2>&1 &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-T> refs/tags/v1.0.0\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-T> refs/tags/v1.0.0\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo ../upstream\n+\t + ce858e6...1029397 HEAD -> master (forced update)\n+\t - [deleted]         next\n+\t * [new tag]         v1.0.0 -> v1.0.0\n+\t * [new reference]   HEAD -> refs/review/master/topic\n+\t * [new branch]      HEAD -> a/b/c\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd upstream &&\n+\t\tgit show-ref\n+\t) >out &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<COMMIT-T> refs/tags/v1.0.0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/transport.c b/transport.c\nindex 1fdc7dac1a..b5b7bb841e 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -501,7 +501,8 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \telse if (is_null_oid(&ref->old_oid))\n \t\tprint_ref_status('*',\n \t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n-\t\t\t\"[new branch]\"),\n+\t\t\t(starts_with(ref->name, \"refs/heads/\") ? \"[new branch]\" :\n+\t\t\t\"[new reference]\")),\n \t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n-- \n2.26.0.rc1.33.g4914ba4bcf\n\n"},{"id":"393708","messageId":"20200322131815.11872-4-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200313122318.78000-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v4 3/5] refs.c: refactor to reuse ref_is_hidden()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-22T13:18:13Z","receivedAt":"2020-03-22T13:18:33Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd new function `ref_is_matched()` to reuse `ref_is_hidden()`. Will use\nthis function for `receive-pack` to check commands with specific\nprefixes.\n\nTest case t5512 covered this change.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n refs.c | 11 ++++++++---\n refs.h |  1 +\n 2 files changed, 9 insertions(+), 3 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 1ab0bb54d3..229159ea1a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1389,13 +1389,18 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti\n }\n \n int ref_is_hidden(const char *refname, const char *refname_full)\n+{\n+\treturn ref_is_matched(hide_refs, refname, refname_full);\n+}\n+\n+int ref_is_matched(struct string_list *match_refs, const char *refname, const char *refname_full)\n {\n \tint i;\n \n-\tif (!hide_refs)\n+\tif (!match_refs)\n \t\treturn 0;\n-\tfor (i = hide_refs->nr - 1; i >= 0; i--) {\n-\t\tconst char *match = hide_refs->items[i].string;\n+\tfor (i = match_refs->nr - 1; i >= 0; i--) {\n+\t\tconst char *match = match_refs->items[i].string;\n \t\tconst char *subject;\n \t\tint neg = 0;\n \t\tconst char *p;\ndiff --git a/refs.h b/refs.h\nindex 545029c6d8..a2ea043f7f 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -739,6 +739,7 @@ int parse_hide_refs_config(const char *var, const char *value, const char *);\n  * parameter always points to the full ref name.\n  */\n int ref_is_hidden(const char *, const char *);\n+int ref_is_matched(struct string_list *, const char *, const char *);\n \n enum ref_type {\n \tREF_TYPE_PER_WORKTREE,\t  /* refs inside refs/ but not shared       */\n-- \n2.26.0.rc1.33.g4914ba4bcf\n\n"},{"id":"393709","messageId":"20200322131815.11872-5-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200313122318.78000-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v4 4/5] receive-pack: new config receive.procReceiveRefs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-22T13:18:14Z","receivedAt":"2020-03-22T13:18:33Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd a new multi-valued config variable \"receive.procReceiveRefs\"\nfor `receive-pack` command, like the follows:\n\n    git config --system --add receive.procReceiveRefs refs/for/\n    git config --system --add receive.procReceiveRefs refs/drafts/\n\nIf the specific prefix strings match the reference names of the commands\nwhich are sent by git client to `receive-pack`, these commands will be\nexecuted by an external hook (named \"proc-receive\"), instead of the\ninternal `execute_commands` function.\n\nFor example, if it is set to \"refs/for/\", pushing to a reference such as\n\"refs/for/master\" will not create or update reference \"refs/for/master\",\nbut may create or update a pull request directly by running the external\nhook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/config/receive.txt |  14 +++\n builtin/receive-pack.c           |  43 +++++--\n t/t5411-proc-receive-hook.sh     | 204 +++++++++++++++++++++++++++++++\n 3 files changed, 254 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/receive.txt b/Documentation/config/receive.txt\nindex 65f78aac37..0178f2d478 100644\n--- a/Documentation/config/receive.txt\n+++ b/Documentation/config/receive.txt\n@@ -114,6 +114,20 @@ receive.hideRefs::\n \tAn attempt to update or delete a hidden ref by `git push` is\n \trejected.\n \n+receive.procReceiveRefs::\n+\tThis is a multi-valued variable that defines reference prefixes\n+\tto match the commands in `receive-pack`.  Commands matching the\n+\tprefixes will be executed by an external hooks \"proc-receive\",\n+\tinstead of the internal `execute_commands` function.  If this\n+\tvariable is not defined, the \"proc-receive\" hook will never be\n+\tused, and all commands will be executed by the internal\n+\t`execute_commands` function.\n+\n+\tFor example, if this variable is set to \"refs/for/\", pushing to\n+\treference such as \"refs/for/master\" will not create or update a\n+\treference named \"refs/for/master\", but may create or update a\n+\tpull request directly by running an external hook.\n+\n receive.updateServerInfo::\n \tIf set to true, git-receive-pack will run git-update-server-info\n \tafter receiving data from git-push and updating refs.\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 0aca08eb65..fef97e6985 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -76,6 +76,7 @@ static struct object_id push_cert_oid;\n static struct signature_check sigcheck;\n static const char *push_cert_nonce;\n static const char *cert_nonce_seed;\n+static struct string_list proc_receive_refs;\n \n static const char *NONCE_UNSOLICITED = \"UNSOLICITED\";\n static const char *NONCE_BAD = \"BAD\";\n@@ -228,6 +229,20 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (strcmp(var, \"receive.procreceiverefs\") == 0) {\n+\t\tchar *prefix;\n+\t\tint len;\n+\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tprefix = xstrdup(value);\n+\t\tlen = strlen(prefix);\n+\t\twhile (len && prefix[len - 1] == '/')\n+\t\t\tprefix[--len] = '\\0';\n+\t\tstring_list_insert(&proc_receive_refs, prefix);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -1738,15 +1753,26 @@ static void execute_commands(struct command **orig_commands,\n \t/* Try to find commands that have special prefix in their reference names,\n \t * and mark them to run an external \"proc-receive\" hook later.\n \t */\n-\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n-\t\t\tcontinue;\n+\tif (proc_receive_refs.nr > 0) {\n+\t\tstruct strbuf refname_full = STRBUF_INIT;\n+\t\tsize_t prefix_len;\n+\n+\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n+\t\tprefix_len = refname_full.len;\n \n-\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n-\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n-\t\t\tcmd->run_proc_receive = 1;\n-\t\t\trun_proc_receive = 1;\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tif (!should_process_cmd(cmd))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_setlen(&refname_full, prefix_len);\n+\t\t\tstrbuf_addstr(&refname_full, cmd->ref_name);\n+\t\t\tif (ref_is_matched(&proc_receive_refs, cmd->ref_name, refname_full.buf)) {\n+\t\t\t\tcmd->run_proc_receive = 1;\n+\t\t\t\trun_proc_receive = 1;\n+\t\t\t}\n \t\t}\n+\n+\t\tstrbuf_release(&refname_full);\n \t}\n \n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n@@ -2207,6 +2233,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n+\tstring_list_init(&proc_receive_refs, 0);\n+\n \tpacket_trace_identity(\"receive-pack\");\n \n \targc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);\n@@ -2322,5 +2350,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \toid_array_clear(&shallow);\n \toid_array_clear(&ref);\n \tfree((void *)push_cert_nonce);\n+\tstring_list_clear(&proc_receive_refs, 0);\n \treturn 0;\n }\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex d114559792..f9681bed34 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -168,6 +168,14 @@ test_expect_success \"cleanup\" '\n \t)\n '\n \n+test_expect_success \"add two receive.procReceiveRefs settings\" '\n+\t(\n+\t\tcd upstream &&\n+\t\tgit config --add receive.procReceiveRefs refs/for/ &&\n+\t\tgit config --add receive.procReceiveRefs refs/review/\n+\t)\n+'\n+\n test_expect_success \"no proc-receive hook, fail to push special ref\" '\n \t(\n \t\tcd workbench &&\n@@ -669,4 +677,200 @@ test_expect_success \"push with options\" '\n \ttest_cmp expect actual\n '\n \n+test_expect_success \"cleanup\" '\n+\t(\n+\t\tcd upstream &&\n+\t\tgit update-ref -d refs/heads/next\n+\t)\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+\tchmod a+x upstream/hooks/proc-receive\n+'\n+\n+test_expect_success \"report update of all special refs\" '\n+\t(\n+\t\tcd workbench &&\n+\t\tgit push origin \\\n+\t\t\tHEAD:refs/for/next/topic \\\n+\t\t\tHEAD:refs/review/a/b/c/topic \\\n+\t\t\tHEAD:refs/for/master/topic\n+\t) >out 2>&1 &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo ../upstream\n+\t * [new reference]   HEAD -> refs/for/next/topic\n+\t * [new reference]   HEAD -> refs/review/a/b/c/topic\n+\t * [new reference]   HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd upstream &&\n+\t\tgit show-ref\n+\t) >out &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+\tchmod a+x upstream/hooks/proc-receive\n+'\n+\n+test_expect_success \"report mixed refs update (head first)\" '\n+\t(\n+\t\tcd workbench &&\n+\t\tgit push origin \\\n+\t\t\tHEAD:refs/heads/zzz \\\n+\t\t\tHEAD:refs/for/next/topic \\\n+\t\t\tHEAD:refs/heads/yyy \\\n+\t\t\tHEAD:refs/for/master/topic\n+\t) >out 2>&1 &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/zzz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/yyy\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/zzz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/yyy\n+\tTo ../upstream\n+\t * [new branch]      HEAD -> zzz\n+\t * [new reference]   HEAD -> refs/for/next/topic\n+\t * [new branch]      HEAD -> yyy\n+\t * [new reference]   HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd upstream &&\n+\t\tgit show-ref\n+\t) >out &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/yyy\n+\t<COMMIT-A> refs/heads/zzz\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup\" '\n+\t(\n+\t\tcd upstream &&\n+\t\tgit update-ref -d refs/heads/yyy &&\n+\t\tgit update-ref -d refs/heads/zzz\n+\t)\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+\tchmod a+x upstream/hooks/proc-receive\n+'\n+\n+test_expect_success \"report mixed refs update (special ref first)\" '\n+\t(\n+\t\tcd workbench &&\n+\t\tgit push origin \\\n+\t\t\tHEAD:refs/for/next/topic \\\n+\t\t\t$B:refs/heads/zzz \\\n+\t\t\tHEAD:refs/review/a/b/c/topic \\\n+\t\t\tHEAD:refs/heads/yyy \\\n+\t\t\tHEAD:refs/for/master/topic\n+\t) >out 2>&1 &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/heads/zzz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/yyy\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/heads/zzz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/yyy\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo ../upstream\n+\t * [new reference]   HEAD -> refs/for/next/topic\n+\t * [new branch]      <COMMIT-B> -> zzz\n+\t * [new reference]   HEAD -> refs/review/a/b/c/topic\n+\t * [new branch]      HEAD -> yyy\n+\t * [new reference]   HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd upstream &&\n+\t\tgit show-ref\n+\t) >out &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/yyy\n+\t<COMMIT-B> refs/heads/zzz\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.26.0.rc1.33.g4914ba4bcf\n\n"},{"id":"393710","messageId":"20200322131815.11872-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200313122318.78000-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v4 2/5] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-22T13:18:12Z","receivedAt":"2020-03-22T13:18:33Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nGit calls an internal `execute_commands` function to handle commands\nsent from client to `git-receive-pack`.  Regardless of what references\nthe user pushes, git creates or updates the corresponding references if\nthe user has write-permission.  A contributor who has no\nwrite-permission, cannot push to the repository directly.  So, the\ncontributor has to write commits to an alternate location, and sends\npull request by emails or by other ways.  We call this workflow as a\ndistributed workflow.\n\nIt would be more convenient to work in a centralized workflow like what\nGerrit provided for some cases.  For example, a read-only user who\ncannot push to a branch directly can run the following `git push`\ncommand to push commits to a pseudo reference (has a prefix \"refs/for/\",\nnot \"refs/heads/\") to create a code review.\n\n    git push origin \\\n        HEAD:refs/for/<branch-name>/<session>\n\nThe `<branch-name>` in the above example can be as simple as \"master\",\nor a more complicated branch name like \"foo/bar\".  The `<session>` in\nthe above example command can be the local branch name of the client\nside, such as \"my/topic\".\n\nWe cannot implement a centralized workflow elegantly by using\n\"pre-receive\" + \"post-receive\", because Git will call the internal\nfunction \"execute_commands\" to create references (even the special\npseudo reference) between these two hooks.  Even though we can delete\nthe temporarily created pseudo reference via the \"post-receive\" hook,\nhaving a temporary reference is not safe for concurrent pushes.\n\nSo, add a filter and a new handler to support this kind of workflow.\nThe filter will check the prefix of the reference name, and if the\ncommand has a special reference name, the filter will turn a specific\nfield (`run_proc_receive`) on for the command.  Commands with this filed\nturned on will be executed by a new handler (an hook named\n\"proc-receive\") instead of the internal `execute_commands` function.\nWe can use this \"proc-receive\" command to create pull requests or send\nemails for code review.\n\nThis \"proc-receive\" hook reads commands, push-options (optional), and\nsend result using a protocol in pkt-line format.  In the following\nexample, The letter \"S\" stands for \"receive-pack\" and letter \"H\" stands\nfor the hook.\n\n    S: PKT-LINE(version=1\\0push-options ...)\n    S: flush-pkt\n\n    H: PKT-LINE(version=1\\0push-options ...)\n    H: flush-pkt\n\n    S: PKT-LINE(old-oid new-oid ref)\n    S: ... ...\n    S: flush-pkt\n\n    # Optional, only if push-options is negotiated.\n    S: PKT-LINE(push-option)\n    S: ... ...\n    S: flush-pkt\n\n    # OK, run this command successfully.\n    H: PKT-LINE(old-oid new-oid ref ok)\n\n    # NO, I reject it.\n    H: PKT-LINE(old-oid new-oid ref ng reason)\n\n    # OK, but use an alternate reference. (in latter commit)\n    H: PKT-LINE(old-oid new-oid ref ok ref:alt-ref)\n\n    # It will fallthrough to receive-pack to execute. (in latter commit)\n    H: PKT-LINE(old-oid new-oid ref ft)\n\n    H: ... ...\n    H: flush-pkt\n\nAfter receiving a command, the hook can create/update another alternate\nreference.  For example, a command for a reference \"refs/for/master\" may\ncreate a special reference, such as \"refs/pull/123/head\".  The alternate\nreference can be returned from the result in an extensible format like\n\"<old-oid> <new-oid> <reference> <status> [<message>]\".\n\nThe result will be stored in a command list, and \"receive-pack\" will use\nthe result to replace the commands that have specific `run_proc_receive`\nfield turned on.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Makefile                     |   1 +\n builtin/receive-pack.c       | 275 +++++++++++++++++-\n t/helper/test-proc-receive.c | 164 +++++++++++\n t/helper/test-tool.c         |   1 +\n t/helper/test-tool.h         |   1 +\n t/t5411-proc-receive-hook.sh | 528 +++++++++++++++++++++++++++++++++++\n 6 files changed, 965 insertions(+), 5 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n\ndiff --git a/Makefile b/Makefile\nindex 9804a0758b..0a08a0504e 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -725,6 +725,7 @@ TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\n+TEST_BUILTINS_OBJS += test-proc-receive.o\n TEST_BUILTINS_OBJS += test-progress.o\n TEST_BUILTINS_OBJS += test-reach.o\n TEST_BUILTINS_OBJS += test-read-cache.o\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 2cc18bbffd..0aca08eb65 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -312,7 +312,8 @@ struct command {\n \tstruct command *next;\n \tconst char *error_string;\n \tunsigned int skip_update:1,\n-\t\t     did_not_exist:1;\n+\t\t     did_not_exist:1,\n+\t\t     run_proc_receive:1;\n \tint index;\n \tstruct object_id old_oid;\n \tstruct object_id new_oid;\n@@ -817,6 +818,241 @@ static int run_update_hook(struct command *cmd)\n \treturn finish_command(&proc);\n }\n \n+static int read_proc_receive_result(struct packet_reader *reader,\n+\t\t\t\t    struct command **commands)\n+{\n+\tstruct command **tail = commands;\n+\tint code = 0;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tstruct command *cmd;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\t\tchar *status;\n+\t\tchar *msg = NULL;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ')\n+\t\t\tdie(\"protocol error: proc-receive expected 'old new ref status [msg]', got '%s'\",\n+\t\t\t    reader->line);\n+\n+\t\trefname = p;\n+\t\tstatus = strchr(p, ' ');\n+\t\tif (!status)\n+\t\t\tdie(\"protocol error: proc-receive expected 'old new ref status [msg]', got '%s'\",\n+\t\t\t    reader->line);\n+\t\t*status++ = '\\0';\n+\t\tif (strlen(status) > 2 && *(status + 2) == ' ') {\n+\t\t\tmsg = status + 2;\n+\t\t\t*msg++ = '\\0';\n+\t\t}\n+\t\tif (strlen(status) != 2)\n+\t\t\tdie(\"protocol error: proc-receive has bad status '%s' for '%s'\",\n+\t\t\t    status, reader->line);\n+\n+\t\tFLEX_ALLOC_MEM(cmd, ref_name, refname, strlen(refname));\n+\t\toidcpy(&cmd->old_oid, &old_oid);\n+\t\toidcpy(&cmd->new_oid, &new_oid);\n+\t\tcmd->run_proc_receive = 1;\n+\n+\t\tif (!strcmp(status, \"ng\")) {\n+\t\t\tif (msg)\n+\t\t\t\tcmd->error_string = xstrdup(msg);\n+\t\t\telse\n+\t\t\t\tcmd->error_string = \"failed\";\n+\t\t\tcode = 1;\n+\t\t} else if (strcmp(\"ok\", status)) {\n+\t\t\tdie(\"protocol error: proc-receive has bad status '%s' for '%s'\",\n+\t\t\t    status, reader->line);\n+\t\t}\n+\n+\t\t*tail = cmd;\n+\t\ttail = &cmd->next;\n+\t}\n+\treturn code;\n+}\n+\n+static int run_proc_receive_hook(struct command **commands,\n+\t\t\t\t const struct string_list *push_options)\n+{\n+\tstruct child_process proc = CHILD_PROCESS_INIT;\n+\tstruct async muxer;\n+\tstruct command *result_commands = NULL;\n+\tstruct command *cmd;\n+\tconst char *argv[2];\n+\tstruct packet_reader reader;\n+\tstruct strbuf cap = STRBUF_INIT;\n+\tint pr_use_push_options = 0;\n+\tint version = 0;\n+\tint code;\n+\n+\targv[0] = find_hook(\"proc-receive\");\n+\tif (!argv[0]) {\n+\t\trp_error(\"cannot to find hook 'proc-receive'\");\n+\t\treturn 1;\n+\t}\n+\targv[1] = NULL;\n+\n+\tproc.argv = argv;\n+\tproc.in = -1;\n+\tproc.out = -1;\n+\tproc.trace2_hook_name = \"proc-receive\";\n+\n+\tif (use_sideband) {\n+\t\tmemset(&muxer, 0, sizeof(muxer));\n+\t\tmuxer.proc = copy_to_sideband;\n+\t\tmuxer.in = -1;\n+\t\tcode = start_async(&muxer);\n+\t\tif (code)\n+\t\t\treturn code;\n+\t\tproc.err = muxer.in;\n+\t} else {\n+\t\tproc.err = 0;\n+\t}\n+\n+\tcode = start_command(&proc);\n+\tif (code) {\n+\t\tif (use_sideband)\n+\t\t\tfinish_async(&muxer);\n+\t\treturn code;\n+\t}\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\n+\t/* Version negotiaton */\n+\tpacket_reader_init(&reader, proc.out, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\tif (use_push_options)\n+\t\tstrbuf_addstr(&cap, \" push-options\");\n+\tif (use_atomic)\n+\t\tstrbuf_addstr(&cap, \" atomic\");\n+\tif (cap.len) {\n+\t\tpacket_write_fmt(proc.in, \"version=1%c%s\\n\", '\\0', cap.buf + 1);\n+\t\tstrbuf_release(&cap);\n+\t} else {\n+\t\tpacket_write_fmt(proc.in, \"version=1\\n\");\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader.pktlen > 8 && starts_with(reader.line, \"version=\")) {\n+\t\t\tversion = atoi(reader.line + 8);\n+\t\t\tlinelen = strlen(reader.line);\n+\t\t\tif (linelen < reader.pktlen) {\n+\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tpr_use_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (version != 1)\n+\t\tdie(\"protocol error: unknown proc-receive version '%d'\", version);\n+\n+\t/* Send commands */\n+\tfor (cmd = *commands; cmd; cmd = cmd->next) {\n+\t\tchar *old_hex, *new_hex;\n+\n+\t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)\n+\t\t\tcontinue;\n+\n+\t\told_hex = oid_to_hex(&cmd->old_oid);\n+\t\tnew_hex = oid_to_hex(&cmd->new_oid);\n+\n+\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n+\t\t\t\t old_hex, new_hex, cmd->ref_name);\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\t/* Send push options */\n+\tif (pr_use_push_options) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tfor_each_string_list_item(item, push_options)\n+\t\t\tpacket_write_fmt(proc.in, \"%s\", item->string);\n+\n+\t\tpacket_flush(proc.in);\n+\t}\n+\n+\t/* Read result from proc-receive */\n+\tcode = read_proc_receive_result(&reader, &result_commands);\n+\tclose(proc.in);\n+\tclose(proc.out);\n+\tif (use_sideband)\n+\t\tfinish_async(&muxer);\n+\tif (finish_command(&proc))\n+\t\tdie(\"proc-receive did not exit properly\");\n+\n+\tsigchain_pop(SIGPIPE);\n+\n+\t/* After receiving the result from the \"proc-receive\" hook,\n+\t * \"receive-pack\" will use the result to replace commands that\n+\t * have specific `run_proc_receive` field.\n+\t */\n+\tfor (cmd = *commands; cmd; cmd = cmd->next)\n+\t\tif (!cmd->run_proc_receive)\n+\t\t\tbreak;\n+\n+\t/* Merge commands with result_commands and sort */\n+\tif (!cmd) {\n+\t\t*commands = result_commands;\n+\t} else {\n+\t\tstruct command *next_cmd = cmd;\n+\t\tstruct command *next_result = result_commands;\n+\t\tstruct command *head = NULL;\n+\t\tstruct command *tail = NULL;\n+\n+\t\tif (!next_result ||\n+\t\t    strcmp(next_cmd->ref_name, next_result->ref_name) < 0) {\n+\t\t\thead = next_cmd;\n+\t\t\tnext_cmd = next_cmd->next;\n+\t\t} else {\n+\t\t\thead = next_result;\n+\t\t\tnext_result = next_result->next;\n+\t\t}\n+\t\ttail = head;\n+\n+\t\tfor (;;) {\n+\t\t\tif (!next_cmd) {\n+\t\t\t\ttail->next = next_result;\n+\t\t\t\tbreak;\n+\t\t\t} else if (next_cmd->run_proc_receive) {\n+\t\t\t\tnext_cmd = next_cmd->next;\n+\t\t\t} else if (!next_result) {\n+\t\t\t\ttail->next = next_cmd;\n+\t\t\t\tnext_cmd = next_cmd->next;\n+\t\t\t\ttail = tail->next;\n+\t\t\t} else {\n+\t\t\t\tif (strcmp(next_cmd->ref_name, next_result->ref_name) < 0) {\n+\t\t\t\t\ttail->next = next_cmd;\n+\t\t\t\t\tnext_cmd = next_cmd->next;\n+\t\t\t\t\ttail = tail->next;\n+\t\t\t\t} else {\n+\t\t\t\t\ttail->next = next_result;\n+\t\t\t\t\tnext_result = next_result->next;\n+\t\t\t\t\ttail = tail->next;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t\t*commands = head;\n+\t}\n+\n+\treturn code;\n+}\n+\n static char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n@@ -1392,7 +1628,7 @@ static void execute_commands_non_atomic(struct command *commands,\n \tstruct strbuf err = STRBUF_INIT;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\ttransaction = ref_transaction_begin(&err);\n@@ -1432,7 +1668,7 @@ static void execute_commands_atomic(struct command *commands,\n \t}\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\tcmd->error_string = update(cmd, si);\n@@ -1458,16 +1694,18 @@ static void execute_commands_atomic(struct command *commands,\n \tstrbuf_release(&err);\n }\n \n-static void execute_commands(struct command *commands,\n+static void execute_commands(struct command **orig_commands,\n \t\t\t     const char *unpacker_error,\n \t\t\t     struct shallow_info *si,\n \t\t\t     const struct string_list *push_options)\n {\n+\tstruct command *commands = *orig_commands;\n \tstruct check_connected_options opt = CHECK_CONNECTED_INIT;\n \tstruct command *cmd;\n \tstruct iterate_data data;\n \tstruct async muxer;\n \tint err_fd = 0;\n+\tint run_proc_receive = 0;\n \n \tif (unpacker_error) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n@@ -1497,6 +1735,20 @@ static void execute_commands(struct command *commands,\n \n \treject_updates_to_hidden(commands);\n \n+\t/* Try to find commands that have special prefix in their reference names,\n+\t * and mark them to run an external \"proc-receive\" hook later.\n+\t */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!should_process_cmd(cmd))\n+\t\t\tcontinue;\n+\n+\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n+\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n+\t\t\tcmd->run_proc_receive = 1;\n+\t\t\trun_proc_receive = 1;\n+\t\t}\n+\t}\n+\n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n \t\t\tif (!cmd->error_string)\n@@ -1523,6 +1775,19 @@ static void execute_commands(struct command *commands,\n \tfree(head_name_to_free);\n \thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, NULL, NULL);\n \n+\tif (run_proc_receive) {\n+\t\tint code;\n+\n+\t\tcode = run_proc_receive_hook(orig_commands, push_options);\n+\t\tcommands = *orig_commands;\n+\t\tif (code) {\n+\t\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\t\tif (!cmd->error_string  && (cmd->run_proc_receive || use_atomic))\n+\t\t\t\t\tcmd->error_string = \"fail to run proc-receive hook\";\n+\t\t\t}\n+\t\t}\n+\t}\n+\n \tif (use_atomic)\n \t\texecute_commands_atomic(commands, si);\n \telse\n@@ -2019,7 +2284,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\t\tupdate_shallow_info(commands, &si, &ref);\n \t\t}\n \t\tuse_keepalive = KEEPALIVE_ALWAYS;\n-\t\texecute_commands(commands, unpack_status, &si,\n+\t\texecute_commands(&commands, unpack_status, &si,\n \t\t\t\t &push_options);\n \t\tif (pack_lockfile)\n \t\t\tunlink_or_warn(pack_lockfile);\ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nnew file mode 100644\nindex 0000000000..e725cf9713\n--- /dev/null\n+++ b/t/helper/test-proc-receive.c\n@@ -0,0 +1,164 @@\n+#include \"cache.h\"\n+#include \"connect.h\"\n+#include \"parse-options.h\"\n+#include \"pkt-line.h\"\n+#include \"string-list.h\"\n+#include \"test-tool.h\"\n+\n+static const char *proc_receive_usage[] = {\n+\t\"test-tool proc-receive [<options>...]\",\n+\tNULL\n+};\n+\n+static int version = 1;\n+static int verbose = 0;\n+static int no_push_options = 0;\n+static int use_push_options = 0;\n+static struct string_list returns = STRING_LIST_INIT_NODUP;\n+\n+struct command {\n+\tstruct command *next;\n+\tconst char *error_string;\n+\tunsigned int skip_update:1,\n+\t\t     did_not_exist:1;\n+\tint index;\n+\tstruct object_id old_oid;\n+\tstruct object_id new_oid;\n+\tchar ref_name[FLEX_ARRAY]; /* more */\n+};\n+\n+static void proc_receive_verison(struct packet_reader *reader) {\n+\tint server_version = 0;\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader->pktlen > 8 && starts_with(reader->line, \"version=\")) {\n+\t\t\tserver_version = atoi(reader->line+8);\n+\t\t\tlinelen = strlen(reader->line);\n+\t\t\tif (linelen < reader->pktlen) {\n+\t\t\t\tconst char *feature_list = reader->line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tuse_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (server_version != 1)\n+\t\tdie(\"bad protocol version: %d\", server_version);\n+\n+\tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n+\t\t\t version, '\\0',\n+\t\t\t use_push_options && !no_push_options ? \"push-options\": \"\");\n+\tpacket_flush(1);\n+}\n+\n+static void proc_receive_read_commands(struct packet_reader *reader,\n+\t\t\t\t       struct command **commands)\n+{\n+\tstruct command **tail = commands;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tstruct command *cmd;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ')\n+\t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n+\t\t\t    reader->line);\n+\t\trefname = p;\n+\t\tFLEX_ALLOC_MEM(cmd, ref_name, refname, strlen(refname));\n+\t\toidcpy(&cmd->old_oid, &old_oid);\n+\t\toidcpy(&cmd->new_oid, &new_oid);\n+\n+\t\t*tail = cmd;\n+\t\ttail = &cmd->next;\n+\t}\n+}\n+\n+static void proc_receive_read_push_options(struct packet_reader *reader,\n+\t\t\t\t\t   struct string_list *options)\n+{\n+\n+\tif (no_push_options || !use_push_options)\n+\t       return;\n+\n+\twhile (1) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tstring_list_append(options, reader->line);\n+\t}\n+}\n+\n+int cmd__proc_receive(int argc, const char **argv)\n+{\n+\tstruct packet_reader reader;\n+\tstruct command *commands;\n+\tstruct string_list push_options = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"no-push-options\", &no_push_options,\n+\t\t\t \"disable push options\"),\n+\t\tOPT_STRING_LIST('r', \"return\", &returns, \"old/new/ref/status/msg\",\n+\t\t\t\t\"return of results\"),\n+\t\tOPT__VERBOSE(&verbose, \"be verbose\"),\n+\t\tOPT_INTEGER('V', \"version\", &version,\n+\t\t\t    \"use this protocol version number\"),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, \"test-tools\", options, proc_receive_usage, 0);\n+\tif (argc > 0)\n+\t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n+\n+\tpacket_reader_init(&reader, 0, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tproc_receive_verison(&reader);\n+\tproc_receive_read_commands(&reader, &commands);\n+\tproc_receive_read_push_options(&reader, &push_options);\n+\n+\tif (verbose) {\n+\t\tstruct command *cmd;\n+\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tchar *old_hex, *new_hex;\n+\n+\t\t\told_hex = oid_to_hex(&cmd->old_oid);\n+\t\t\tnew_hex = oid_to_hex(&cmd->new_oid);\n+\t\t\tfprintf(stderr, \"proc-receive< %s %s %s\\n\",\n+\t\t\t\told_hex, new_hex, cmd->ref_name);\n+\t\t}\n+\n+\t\tif (push_options.nr > 0) {\n+\t\t\tfor_each_string_list_item(item, &push_options)\n+\t\t\t\tfprintf(stderr, \"proc-receive< %s\\n\", item->string);\n+\t\t}\n+\n+\t\tif (returns.nr) {\n+\t\t\tfor_each_string_list_item(item, &returns)\n+\t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n+\t\t}\n+\t}\n+\n+\tif (returns.nr) {\n+\t\tfor_each_string_list_item(item, &returns)\n+\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n+\t}\n+\tpacket_flush(1);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex c9a232d238..9bff461446 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -43,6 +43,7 @@ static struct test_cmd cmds[] = {\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\n+\t{ \"proc-receive\", cmd__proc_receive},\n \t{ \"progress\", cmd__progress },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex c8549fd87f..71e5f1c372 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -33,6 +33,7 @@ int cmd__parse_pathspec_file(int argc, const char** argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\n+int cmd__proc_receive(int argc, const char **argv);\n int cmd__progress(int argc, const char **argv);\n int cmd__reach(int argc, const char **argv);\n int cmd__read_cache(int argc, const char **argv);\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex fe2861f2e6..d114559792 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -50,6 +50,24 @@ format_git_output () {\n \t\t-e \"s/'/\\\"/g\"\n }\n \n+# Asynchronous sideband may generate inconsistent output messages,\n+# sort before comparison.\n+test_sorted_cmp () {\n+\tif ! $GIT_TEST_CMP \"$@\"\n+\tthen\n+\t\tcmd=$GIT_TEST_CMP\n+\t\tfor f in \"$@\"\n+\t\tdo\n+\t\t\tsort \"$f\" >\"$f.sorted\"\n+\t\t\tcmd=\"$cmd \\\"$f.sorted\\\"\"\n+\t\tdone\n+\t\tif ! eval $cmd\n+\t\tthen\n+\t\t\t$GIT_TEST_CMP \"$@\"\n+\t\tfi\n+\tfi\n+}\n+\n test_expect_success \"setup\" '\n \tgit init --bare upstream &&\n \tgit init workbench &&\n@@ -141,4 +159,514 @@ test_expect_success \"normal git-push command\" '\n \ttest_cmp expect actual\n '\n \n+test_expect_success \"cleanup\" '\n+\t(\n+\t\tcd upstream &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v1.0.0 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\n+\n+test_expect_success \"no proc-receive hook, fail to push special ref\" '\n+\t(\n+\t\tcd workbench &&\n+\t\ttest_must_fail git push origin \\\n+\t\t\tHEAD:next \\\n+\t\t\tHEAD:refs/for/master/topic\n+\t) >out 2>&1 &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot to find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo ../upstream\n+\t * [new branch]      HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\terror: failed to push some refs to \"../upstream\"\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd upstream &&\n+\t\tgit show-ref\n+\t) >out &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup\" '\n+\t(\n+\t\tcd upstream &&\n+\t\tgit update-ref -d refs/heads/next\n+\t)\n+'\n+\n+# TODO: report for the failure of master branch is unnecessary.\n+test_expect_success \"no proc-receive hook, fail all for atomic push\" '\n+\t(\n+\t\tcd workbench &&\n+\t\ttest_must_fail git push --atomic origin \\\n+\t\t\tHEAD:next \\\n+\t\t\tHEAD:refs/for/master/topic\n+\t) >out 2>&1 &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot to find hook \"proc-receive\"\n+\tTo ../upstream\n+\t ! [rejected]        master (atomic push failed)\n+\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\terror: failed to push some refs to \"../upstream\"\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd upstream &&\n+\t\tgit show-ref\n+\t) >out &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad version)\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+\tchmod a+x upstream/hooks/proc-receive\n+'\n+\n+test_expect_success \"proc-receive bad protocol: unknown version\" '\n+\t(\n+\t\tcd workbench &&\n+\t\ttest_must_fail git push origin \\\n+\t\t\tHEAD:refs/for/master/topic\n+\t) >out 2>&1 &&\n+\tformat_git_output <out | grep \"protocol error\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tfatal: protocol error: unknown proc-receive version \"2\"\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd upstream &&\n+\t\tgit show-ref\n+\t) >out &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report)\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+test_expect_success \"proc-receive bad protocol: no report\" '\n+\t(\n+\t\tcd workbench &&\n+\t\ttest_must_fail git push origin \\\n+\t\t\tHEAD:refs/for/master/topic\n+\t) >out 2>&1 &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo ../upstream\n+\t ! [remote failure]  HEAD -> refs/for/master/topic (remote failed to report status)\n+\terror: failed to push some refs to \"../upstream\"\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd upstream &&\n+\t\tgit show-ref\n+\t) >out &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad oid)\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"bad-id new-id ref ok\"\n+\tEOF\n+'\n+\n+test_expect_success \"proc-receive bad protocol: bad oid\" '\n+\t(\n+\t\tcd workbench &&\n+\t\ttest_must_fail git push origin \\\n+\t\t\tHEAD:refs/for/master/topic\n+\t) >out 2>&1 &&\n+\tformat_git_output <out | grep \"protocol error\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tfatal: protocol error: proc-receive expected \"old new ref status [msg]\", got \"bad-id new-id ref ok\"\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd upstream &&\n+\t\tgit show-ref\n+\t) >out &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no status)\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic\"\n+\tEOF\n+'\n+\n+test_expect_success \"proc-receive bad protocol: no status\" '\n+\t(\n+\t\tcd workbench &&\n+\t\ttest_must_fail git push origin \\\n+\t\t\tHEAD:refs/for/master/topic\n+\t) >out 2>&1 &&\n+\tformat_git_output <out | grep \"protocol error\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tfatal: protocol error: proc-receive expected \"old new ref status [msg]\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd upstream &&\n+\t\tgit show-ref\n+\t) >out &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status)\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic xx msg\"\n+\tEOF\n+'\n+\n+test_expect_success \"proc-receive bad protocol: unknown status\" '\n+\t(\n+\t\tcd workbench &&\n+\t\ttest_must_fail git push origin \\\n+\t\t\tHEAD:refs/for/master/topic\n+\t) >out 2>&1 &&\n+\tformat_git_output <out | grep \"protocol error\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tfatal: protocol error: proc-receive has bad status \"xx\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd upstream &&\n+\t\tgit show-ref\n+\t) >out &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad status)\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic bad status\"\n+\tEOF\n+'\n+\n+test_expect_success \"proc-receive bad protocol: bad status\" '\n+\t(\n+\t\tcd workbench &&\n+\t\ttest_must_fail git push origin \\\n+\t\t\tHEAD:refs/for/master/topic\n+\t) >out 2>&1 &&\n+\tformat_git_output <out | grep \"protocol error\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tfatal: protocol error: proc-receive has bad status \"bad status\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd upstream &&\n+\t\tgit show-ref\n+\t) >out &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng)\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng\"\n+\tEOF\n+'\n+\n+test_expect_success \"proc-receive: fail to update (no message)\" '\n+\t(\n+\t\tcd workbench &&\n+\t\ttest_must_fail git push origin \\\n+\t\t\tHEAD:refs/for/master/topic\n+\t) >out 2>&1 &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng\n+\tTo ../upstream\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n+\terror: failed to push some refs to \"../upstream\"\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd upstream &&\n+\t\tgit show-ref\n+\t) >out &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message)\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng error msg\"\n+\tEOF\n+'\n+\n+test_expect_success \"proc-receive: fail to update (has message)\" '\n+\t(\n+\t\tcd workbench &&\n+\t\ttest_must_fail git push origin \\\n+\t\t\tHEAD:refs/for/master/topic\n+\t) >out 2>&1 &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng error msg\n+\tTo ../upstream\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n+\terror: failed to push some refs to \"../upstream\"\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd upstream &&\n+\t\tgit show-ref\n+\t) >out &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ok)\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+'\n+\n+test_expect_success \"proc-receive: ok\" '\n+\t(\n+\t\tcd workbench &&\n+\t\tgit push origin \\\n+\t\t\tHEAD:refs/for/master/topic\n+\t) >out 2>&1 &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo ../upstream\n+\t * [new reference]   HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd upstream &&\n+\t\tgit show-ref\n+\t) >out &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"proc-receive: report unknown ref\" '\n+\t(\n+\t\tcd workbench &&\n+\t\ttest_must_fail git push origin \\\n+\t\t\tHEAD:refs/for/a/b/c/my/topic\n+\t) >out 2>&1 &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\twarning: remote reported status on unknown ref: refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo ../upstream\n+\t ! [remote failure]  HEAD -> refs/for/a/b/c/my/topic (remote failed to report status)\n+\terror: failed to push some refs to \"../upstream\"\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd upstream &&\n+\t\tgit show-ref\n+\t) >out &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"not support push options\" '\n+\t(\n+\t\tcd workbench &&\n+\t\ttest_must_fail git push \\\n+\t\t\t-o issue=123 \\\n+\t\t\t-o reviewer=user1 \\\n+\t\t\torigin \\\n+\t\t\tHEAD:refs/for/master/topic\n+\t) >out 2>&1 &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tfatal: the receiving end does not support push options\n+\tfatal: the remote end hung up unexpectedly\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd upstream &&\n+\t\tgit show-ref\n+\t) >out &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options\" '\n+\t(\n+\t\tcd upstream &&\n+\t\tgit config receive.advertisePushOptions true\n+\t)\n+'\n+\n+test_expect_success \"push with options\" '\n+\t(\n+\t\tcd workbench &&\n+\t\tgit push \\\n+\t\t\t-o issue=123 \\\n+\t\t\t-o reviewer=user1 \\\n+\t\t\torigin \\\n+\t\t\tHEAD:refs/heads/next \\\n+\t\t\tHEAD:refs/for/master/topic\n+\t) >out 2>&1 &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo ../upstream\n+\t * [new branch]      HEAD -> next\n+\t * [new reference]   HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd upstream &&\n+\t\tgit show-ref\n+\t) >out &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.26.0.rc1.33.g4914ba4bcf\n\n"},{"id":"393711","messageId":"20200322131815.11872-6-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200313122318.78000-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v4 5/5] receive-pack: refactor report for proc-receive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-22T13:18:15Z","receivedAt":"2020-03-22T13:18:54Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThe \"proc-receive\" may update one or more references, and will send its\nresult one by one in pkt-line format.  Each line of the result has four\nfields and one optional message field, as \"<old-oid> <new-oid> <ref>\n<status> [<message>]\".  See the following example:\n\n    # OK, run this command successfully.\n    PKT-LINE(old-oid new-oid ref ok)\n\n    # NO, I reject it.\n    PKT-LINE(old-oid new-oid ref ng reason)\n\n    # OK, but use an alternate reference.\n    PKT-LINE(old-oid new-oid ref ok ref:alt-ref)\n\n    # It will fallthrough to receive-pack to execute.\n    PKT-LINE(old-oid new-oid ref ft)\n\nThe first three fields have the same foramt as a command.\n\nThe forth field has a two-letter status code.  Available status code:\n\n* ok: The command runs successfully.  If the optional message has a\n  prefix \"ref:\", the hook has created/updated an alternate reference\n  instead.\n\n* ng: Fail to run the command. Error message is in the optional message\n  field.\n\n* ft: Will fallthrough to receive-pack to execute.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c       | 21 +++++++--\n t/t5411-proc-receive-hook.sh | 90 +++++++++++++++++++++++++++++++++---\n transport.c                  | 27 +++++++----\n 3 files changed, 119 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex fef97e6985..c791f562d0 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -326,6 +326,7 @@ static void write_head_info(void)\n struct command {\n \tstruct command *next;\n \tconst char *error_string;\n+\tconst char *extra_string;\n \tunsigned int skip_update:1,\n \t\t     did_not_exist:1,\n \t\t     run_proc_receive:1;\n@@ -883,7 +884,12 @@ static int read_proc_receive_result(struct packet_reader *reader,\n \t\t\telse\n \t\t\t\tcmd->error_string = \"failed\";\n \t\t\tcode = 1;\n-\t\t} else if (strcmp(\"ok\", status)) {\n+\t\t} else if (!strcmp(\"ok\", status)) {\n+\t\t\tcmd->extra_string = xstrdup_or_null(msg);\n+\t\t} else if (!strcmp(\"ft\", status)) {\n+\t\t\t/* Reset \"run_proc_receive\" field, and continue to run in \"receive-pack\" */\n+\t\t\tcmd->run_proc_receive = 0;\n+\t\t} else {\n \t\t\tdie(\"protocol error: proc-receive has bad status '%s' for '%s'\",\n \t\t\t    status, reader->line);\n \t\t}\n@@ -2190,12 +2196,17 @@ static void report(struct command *commands, const char *unpack_status)\n \tpacket_buf_write(&buf, \"unpack %s\\n\",\n \t\t\t unpack_status ? unpack_status : \"ok\");\n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!cmd->error_string)\n-\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n-\t\t\t\t\t cmd->ref_name);\n-\t\telse\n+\t\tif (!cmd->error_string) {\n+\t\t\tif (!cmd->extra_string)\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n+\t\t\t\t\t\t cmd->ref_name);\n+\t\t\telse\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s%c%s\\n\",\n+\t\t\t\t\t\t cmd->ref_name, ' ', cmd->extra_string);\n+\t\t} else {\n \t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n \t\t\t\t\t cmd->ref_name, cmd->error_string);\n+\t\t}\n \t}\n \tpacket_buf_flush(&buf);\n \ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex f9681bed34..917988ab57 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -691,9 +691,9 @@ test_expect_success \"setup proc-receive hook\" '\n \tprintf >&2 \"# proc-receive hook\\n\"\n \n \ttest-tool proc-receive -v \\\n-\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok ref:refs/pull/123/head\" \\\n \t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n-\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok ref:refs/pull/124/head\"\n \tEOF\n \tchmod a+x upstream/hooks/proc-receive\n '\n@@ -716,17 +716,17 @@ test_expect_success \"report update of all special refs\" '\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n-\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok ref:refs/pull/123/head\n \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n-\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok ref:refs/pull/124/head\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo ../upstream\n-\t * [new reference]   HEAD -> refs/for/next/topic\n+\t * [new reference]   HEAD -> refs/pull/123/head\n \t * [new reference]   HEAD -> refs/review/a/b/c/topic\n-\t * [new reference]   HEAD -> refs/for/master/topic\n+\t * [new reference]   HEAD -> refs/pull/124/head\n \tEOF\n \ttest_cmp expect actual &&\n \t(\n@@ -873,4 +873,82 @@ test_expect_success \"report mixed refs update (special ref first)\" '\n \ttest_cmp expect actual\n '\n \n+test_expect_success \"config receive.procReceiveRefs for all ref/\" '\n+\t(\n+\t\tcd upstream &&\n+\t\tgit config --add receive.procReceiveRefs refs/\n+\t)\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$A $ZERO_OID refs/heads/yyy ft\" \\\n+\t\t-r \"$B $A refs/heads/zzz ft\" \\\n+\t\t-r \"$A $B refs/for/master/topic ok ref:refs/pull/123/head\" \\\n+\t\t-r \"$A $B refs/heads/master ft\" \\\n+\t\t-r \"$B $A refs/for/next/topic ok ref:refs/pull/124/head\"\n+\tEOF\n+\tchmod a+x upstream/hooks/proc-receive\n+'\n+\n+test_expect_success \"report test: fallthrough\" '\n+\t(\n+\t\tcd workbench &&\n+\t\tgit push -f origin \\\n+\t\t\t:refs/heads/yyy \\\n+\t\t\t$A:refs/heads/zzz \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\tHEAD:refs/for/next/topic \\\n+\t\t\t$B:refs/heads/master\n+\t) >out 2>&1 &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/yyy\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/zzz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/yyy\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/zzz\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> <COMMIT-A> <ZERO-OID> refs/heads/yyy ft\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/heads/zzz ft\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/for/master/topic ok ref:refs/pull/123/head\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/heads/master ft\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/next/topic ok ref:refs/pull/124/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/yyy\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/zzz\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/next/topic\n+\tTo ../upstream\n+\t   1029397..ce858e6  <COMMIT-B> -> master\n+\t - [deleted]         yyy\n+\t + ce858e6...1029397 <COMMIT-A> -> zzz (forced update)\n+\t * [new reference]   HEAD -> refs/pull/123/head\n+\t * [new reference]   HEAD -> refs/pull/124/head\n+\tEOF\n+\ttest_cmp expect actual &&\n+\t(\n+\t\tcd upstream &&\n+\t\tgit show-ref\n+\t) >out &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/zzz\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\ndiff --git a/transport.c b/transport.c\nindex b5b7bb841e..d51af11d48 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -463,11 +463,16 @@ static void print_ref_status(char flag, const char *summary,\n \t\t\t     struct ref *to, struct ref *from, const char *msg,\n \t\t\t     int porcelain, int summary_width)\n {\n+\tchar *to_name = to->name;\n+\n+\tif (to->remote_status && !strncmp(\"ref:\", to->remote_status, 4))\n+\t\tto_name = to->remote_status + 4;\n+\n \tif (porcelain) {\n \t\tif (from)\n-\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to_name);\n \t\telse\n-\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to_name);\n \t\tif (msg)\n \t\t\tfprintf(stdout, \"%s (%s)\\n\", summary, msg);\n \t\telse\n@@ -481,9 +486,9 @@ static void print_ref_status(char flag, const char *summary,\n \t\tfprintf(stderr, \" %s%c %-*s%s \", red, flag, summary_width,\n \t\t\tsummary, reset);\n \t\tif (from)\n-\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(to->name));\n+\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(to_name));\n \t\telse\n-\t\t\tfputs(prettify_refname(to->name), stderr);\n+\t\t\tfputs(prettify_refname(to_name), stderr);\n \t\tif (msg) {\n \t\t\tfputs(\" (\", stderr);\n \t\t\tfputs(msg, stderr);\n@@ -498,13 +503,19 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \tif (ref->deletion)\n \t\tprint_ref_status('-', \"[deleted]\", ref, NULL, NULL,\n \t\t\t\t porcelain, summary_width);\n-\telse if (is_null_oid(&ref->old_oid))\n+\telse if (is_null_oid(&ref->old_oid)) {\n+\t\tchar *refname;\n+\n+\t\tif (ref->remote_status && !strncmp(ref->remote_status, \"ref:\", 4))\n+\t\t\trefname = ref->remote_status + 4;\n+\t\telse\n+\t\t\trefname = ref->name;\n \t\tprint_ref_status('*',\n-\t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n-\t\t\t(starts_with(ref->name, \"refs/heads/\") ? \"[new branch]\" :\n+\t\t\t(starts_with(refname, \"refs/tags/\") ? \"[new tag]\" :\n+\t\t\t(starts_with(refname, \"refs/heads/\") ? \"[new branch]\" :\n \t\t\t\"[new reference]\")),\n \t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n-\telse {\n+\t} else {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n \t\tconst char *msg;\n-- \n2.26.0.rc1.33.g4914ba4bcf\n\n"},{"id":"393919","messageId":"xmqqftdxypsu.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200322131815.11872-1-worldhello.net@gmail.com","subject":"Re: [PATCH v4 0/5] New proc-receive hook for centralized workflow","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-03-25T05:19:29Z","receivedAt":"2020-03-25T05:19:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> Changes since v2 and v3:\n>\n> * Implement a on-the-wire protocol, and add new test helper to test\n>   the on-the-wire protocol. See patch 2/5.\n>\n> * Refactor report() on both server and client side for reporting of\n>   proc-receive. See patch 5/5.\n>\n>\n> Jiang Xin (5):\n>   transport: not report a non-head push as a branch\n>   receive-pack: add new proc-receive hook\n>   refs.c: refactor to reuse ref_is_hidden()\n>   receive-pack: new config receive.procReceiveRefs\n>   receive-pack: refactor report for proc-receive\n\nt5516 detects breakage of these patches that are queued directly on\ntop of 2.26.\n\nThe tip of 'pu' tonight includes this, and fails.\n\nE.g. https://travis-ci.org/github/git/git/builds/666579791\n\nThanks.\n"},{"id":"393938","messageId":"xmqq4kudynq4.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200322131815.11872-2-worldhello.net@gmail.com","subject":"Re: [PATCH v4 1/5] transport: not report a non-head push as a branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-03-25T06:04:19Z","receivedAt":"2020-03-25T06:04:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> +# NOTE: Avoid calling this function from a subshell since variable\n> +# assignments will disappear when subshell exits.\n\ns/Avoid/Never/; the test_tick helper used in this function has the\nsame issue.\n\n> +create_commits_in () {\n> +\trepo=\"$1\" &&\n> +\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{} 2>/dev/null)\n\nDo we need to discard the standard error stream like this?  As long\nas this helper function is called inside a test_expect_thing, the\nerror will not be seen and when debugging the test, we would want to\nsee a failure (which indicates that we are creating a root commit).\n\n> +format_git_output () {\n\nI suspect this is to make output from _some_ \"git\" subcommand into a\nsymbolic form so that exact object names would not have to be used\nin comparison, but this obviously cannot take _any_ git subcommand,\nbut a specific one.  The name does not say which one, which is\ndisservice to readers.\n\n> +\tsed \\\n> +\t\t-e \"s/  *\\$//g\" \\\n\nWhat's the point of /g?  You are anchoring the pattern (i.e. one or\nmore SP) to the right end of the line, so it's not like it would\ntransform \"a  b c   \" into \"abc\".  Also it would be sufficient to\nsay \"zero or more\" and that would be shorter, I think, i.e.\n\n\t\t-e 's/ *$//' \\\n\n> +\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n> +\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n> +\t\t-e \"s/$TAG/<COMMIT-T>/g\" \\\n\nA and B are commits, so the symbolic <COMMIT-A> and <COMMIT-B> do\nmake sense, but wouldn't TAG be an annotated tag?  Wouldn't it be\n<TAG-A> perhaps?\n\n> +\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n\nMaekes sense.\n\n> +\t\t-e \"s/'/\\\"/g\"\n\nI am not sure what is going on here.  Why turn <don't> into <don\"t>?\nWithout exactly knowing what is getting munged, a reader cannot tell\nwhat is going on here.  Let's read on to figure it out.\n\nIn any case, I think most of this helper is not about \"formatting\"\noutput, but hiding, getting rid of, redacting or censoring the\ndetails for easier comparison.  I'd prefer to see some different\nverb to be used for its name.\n\n> +}\n> +\n> +test_expect_success \"setup\" '\n> +\tgit init --bare upstream &&\n> +\tgit init workbench &&\n> +\tcreate_commits_in workbench A B &&\n> +\t(\n> +\t\tcd workbench &&\n> +\t\tgit remote add origin ../upstream &&\n> +\t\tgit config core.abbrev 7 &&\n\nThis '7' is \"use at least seven hexdigits\"; is that really what we\nwant?  Depending on chance, some objects may be shown using 8 or\nmore---is our \"munge output into symbolic form for comparison\"\nscript prepared for such a case?\n\n> +\t\tgit update-ref refs/heads/master $A &&\n> +\t\tgit tag -m \"v1.0.0\" v1.0.0 $A &&\n> +\t\tgit push origin \\\n> +\t\t\t$B:refs/heads/master \\\n> +\t\t\t$A:refs/heads/next\n> +\t) &&\n> +\tTAG=$(cd workbench; git rev-parse v1.0.0) &&\n\nWhy not \"git -C workbench rev-parse v1.0.0\"?\n\nSo, at this point, there are two repositories, upstream and\nworkbench, and two commits A and B (B is newer).  workbench has A at\nthe tip of its 'master'; upstream has A at the tip of 'next' and B\n(which is newer than A) at the tip of 'master'.\n\n> +\n> +\t# setup pre-receive hook\n> +\tcat >upstream/hooks/pre-receive <<-EOF &&\n\nWouldn't it make it easier to read the resulting text if you quoted\nthe end-of-here-text marker here, i.e. \"<<\\-EOF\"?  That way, you can\nlose backslash before $old, $new and $ref.\n\n> +\t#!/bin/sh\n> +\n> +\tprintf >&2 \"# pre-receive hook\\n\"\n> +\n> +\twhile read old new ref\n> +\tdo\n> +\t\tprintf >&2 \"pre-receive< \\$old \\$new \\$ref\\n\"\n> +\tdone\n> +\tEOF\n> +\t# setup post-receive hook\n> +\tcat >upstream/hooks/post-receive <<-EOF &&\n\nLikewise.\n\n> +test_expect_success \"normal git-push command\" '\n> +\t(\n> +\t\tcd workbench &&\n> +\t\tgit push -f origin \\\n> +\t\t\trefs/tags/v1.0.0 \\\n> +\t\t\t:refs/heads/next \\\n> +\t\t\tHEAD:refs/heads/master \\\n> +\t\t\tHEAD:refs/review/master/topic \\\n> +\t\t\tHEAD:refs/heads/a/b/c\n> +\t) >out 2>&1 &&\n\nDo we need to worry about progress output (which we would want to\nsquelch, I presume, for the purpose of comparing with the\n\"expectation\")?  Would it be just the matter of giving --no-progress?\n\n> +\tformat_git_output <out >actual &&\n> +\tcat >expect <<-EOF &&\n> +\tremote: # pre-receive hook\n> +\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n> +\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n> +\tremote: pre-receive< <ZERO-OID> <COMMIT-T> refs/tags/v1.0.0\n> +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n> +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n\nDo we guarantee the order in which these received refs are reported,\nor do we somehow need to sort (presumably inside the hook)?  The\nsame question applies to the post-receive side, too.\n\n> +\tremote: # post-receive hook\n> +\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n> +\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n> +\tremote: post-receive< <ZERO-OID> <COMMIT-T> refs/tags/v1.0.0\n> +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n> +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n> +\tTo ../upstream\n> +\t + ce858e6...1029397 HEAD -> master (forced update)\n> +\t - [deleted]         next\n> +\t * [new tag]         v1.0.0 -> v1.0.0\n> +\t * [new reference]   HEAD -> refs/review/master/topic\n> +\t * [new branch]      HEAD -> a/b/c\n> +\tEOF\n> +\ttest_cmp expect actual &&\n> +\t(\n> +\t\tcd upstream &&\n> +\t\tgit show-ref\n\nThis one I know we give output in a reliable order, but I do not\noffhand know if we give any written guarantee.  Perhaps we should\ndocument it if we haven't already (i.e. it is OK the \"expected\"\noutput below assumes that the output is sorted by full refnames in\nASCII order).\n\n> +\t) >out &&\n> +\tformat_git_output <out >actual &&\n> +\tcat >expect <<-EOF &&\n> +\t<COMMIT-A> refs/heads/a/b/c\n> +\t<COMMIT-A> refs/heads/master\n> +\t<COMMIT-A> refs/review/master/topic\n> +\t<COMMIT-T> refs/tags/v1.0.0\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_done\n> diff --git a/transport.c b/transport.c\n> index 1fdc7dac1a..b5b7bb841e 100644\n> --- a/transport.c\n> +++ b/transport.c\n> @@ -501,7 +501,8 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n>  \telse if (is_null_oid(&ref->old_oid))\n>  \t\tprint_ref_status('*',\n>  \t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n> -\t\t\t\"[new branch]\"),\n> +\t\t\t(starts_with(ref->name, \"refs/heads/\") ? \"[new branch]\" :\n> +\t\t\t\"[new reference]\")),\n>  \t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n\nThe original is largely to blame, but I couldn't read the above and\nunderstand what the above is doing, before reformatting it like so:\n\n\telse if (is_null_oid(&ref->old_oid))\n\t\tprint_ref_status('*',\n\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n\t\t\t\t  ? \"[new tag]\"\n\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n\t\t\t\t     ? \"[new branch]\"\n\t\t\t\t     : \"[new reference]\")),\n\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n\nWhen long subexpressions are involved, ternary operator ?: is easier\nto read, especially when nested, if you can see its parse tree when\nyou tilt your head 90-degrees sideways (i.e. the same direction as\nyou can see a smile in ;-).\n\nThanks.\n\n"},{"id":"393968","messageId":"20200325143608.45141-2-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"20200322131815.11872-3-worldhello.net@gmail.com","subject":"[PATCH 1/3] t5543: never report what we do not push","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-25T14:36:06Z","receivedAt":"2020-03-25T14:36:23Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"When we push some references to the git server, we expect git to report\nthe status of the references we are pushing; no more, no less.  But when\npusing with atomic mode, if some references cannot be pushed, Git reports\nthe reject message on all references in the remote repository.\n\nAdd new test cases in t5543, and fix them in latter commit.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5543-atomic-push.sh | 92 ++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 92 insertions(+)\n\ndiff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh\nindex 7079bcf9a0..4b4c0a262b 100755\n--- a/t/t5543-atomic-push.sh\n+++ b/t/t5543-atomic-push.sh\n@@ -27,6 +27,13 @@ test_refs () {\n \ttest_cmp expect actual\n }\n \n+format_git_output () {\n+\tawk '/^(To| !) / {print}' | \\\n+\tsed \\\n+\t\t-e \"s/  *\\$//g\" \\\n+\t\t-e \"s/'/\\\"/g\"\n+}\n+\n test_expect_success 'atomic push works for a single branch' '\n \tmk_repo_pair &&\n \t(\n@@ -191,4 +198,89 @@ test_expect_success 'atomic push is not advertised if configured' '\n \ttest_refs master HEAD@{1}\n '\n \n+# References in upstream : master(1) one(1) foo(1)\n+# References in workbench: master(2)        foo(1) two(2) bar(2)\n+# Atomic push            : master(2)               two(2) bar(2)\n+test_expect_failure 'atomic push reports (reject by update hook)' '\n+\tmk_repo_pair &&\n+\t(\n+\t\tcd workbench &&\n+\t\t# Keep constant output.\n+\t\tgit config core.abbrev 7 &&\n+\t\ttest_commit one &&\n+\t\tgit branch foo &&\n+\t\tgit push up master one foo &&\n+\t\tgit tag -d one\n+\t) &&\n+\t(\n+\t\tmkdir -p upstream/.git/hooks &&\n+\t\tcat >upstream/.git/hooks/update <<-EOF &&\n+\t\t#!/bin/sh\n+\n+\t\tif test \"\\$1\" = \"refs/heads/bar\"\n+\t\tthen\n+\t\t\techo >2 \"Pusing to branch bar is prohibited\"\n+\t\t\texit 1\n+\t\tfi\n+\t\tEOF\n+\t\tchmod a+x upstream/.git/hooks/update\n+\t) &&\n+\t(\n+\t\tcd workbench &&\n+\t\ttest_commit two &&\n+\t\tgit branch bar\n+\t) &&\n+\ttest_must_fail git -C workbench \\\n+\t\tpush --atomic up master two bar >out 2>&1 &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo ../upstream\n+\t ! [remote rejected] master -> master (atomic push failure)\n+\t ! [remote rejected] two -> two (atomic push failure)\n+\t ! [remote rejected] bar -> bar (hook declined)\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# References in upstream : master(1) one(1) foo(1)\n+# References in workbench: master(2)        foo(1) two(2) bar(2)\n+test_expect_failure 'atomic push reports (mirror, but reject by update hook)' '\n+\t(\n+\t\tcd workbench &&\n+\t\tgit remote remove up &&\n+\t\tgit remote add up ../upstream\n+\t) &&\n+\ttest_must_fail git -C workbench \\\n+\t\tpush --atomic --mirror up >out 2>&1 &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo ../upstream\n+\t ! [remote rejected] master -> master (atomic push failure)\n+\t ! [remote rejected] one (atomic push failure)\n+\t ! [remote rejected] bar -> bar (hook declined)\n+\t ! [remote rejected] two -> two (atomic push failure)\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# References in upstream : master(2) one(1) foo(1)\n+# References in workbench: master(1)        foo(1) two(2) bar(2)\n+test_expect_failure 'atomic push reports (reject by non-ff)' '\n+\trm upstream/.git/hooks/update &&\n+\t(\n+\t\tcd workbench &&\n+\t\tgit push up master &&\n+\t\tgit reset --hard HEAD^\n+\t) &&\n+\ttest_must_fail git -C workbench \\\n+\t\tpush --atomic up master foo bar >out 2>&1 &&\n+\tformat_git_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo ../upstream\n+\t ! [rejected]        master -> master (non-fast-forward)\n+\t ! [rejected]        bar -> bar (atomic push failed)\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.26.0.3.ga7a9d752d4\n\n"},{"id":"393969","messageId":"20200325143608.45141-1-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"20200322131815.11872-3-worldhello.net@gmail.com","subject":"[PATCH 0/3] Never report references we not push","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-25T14:36:05Z","receivedAt":"2020-03-25T14:36:24Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"A strange behavior is found when writing test cases for topic\n'jx/proc-receive-hook':\n\n> # TODO: report for the failure of master branch is unnecessary.\n> test_expect_success \"no proc-receive hook, fail all for atomic push\" '\n> \t(\n> \t\tcd workbench &&\n> \t\ttest_must_fail git push --atomic origin \\\n> \t\t\tHEAD:next \\\n> \t\t\tHEAD:refs/for/master/topic\n> \t) >out 2>&1 &&\n> \tformat_git_output <out >actual &&\n> \tcat >expect <<-EOF &&\n> \tremote: # pre-receive hook\n> \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n> \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n> \tremote: error: cannot to find hook \"proc-receive\"\n> \tTo ../upstream\n> \t ! [rejected]        master (atomic push failed)\n\nWe do not push \"master\" branch, but git reports status for it.\n\n> \t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n> \t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n> \terror: failed to push some refs to \"../upstream\"\n> \tEOF\n> \ttest_cmp expect actual &&\n> \t(\n> \t\tcd upstream &&\n> \t\tgit show-ref\n> \t) >out &&\n> \tformat_git_output <out >actual &&\n> \tcat >expect <<-EOF &&\n> \t<COMMIT-A> refs/heads/master\n> \tEOF\n> \ttest_cmp expect actual\n> '\n\nThis issue is introduced by commit v2.22.0-1-g3bca1e7f9f (transport-helper:\nenforce atomic in push_refs_with_push, 2019-07-11).  That commit wanted to\nfix report issue of HTTP protocol, but marked all remote references failure\nfor atomic push.\n\n--\n\nJiang Xin (3):\n  t5543: never report what we do not push\n  send-pack: mark failure of atomic push properly\n  transport-helper: enforce atomic in push_refs_with_push\n\n send-pack.c                |  2 +\n t/t5541-http-push-smart.sh | 12 ++++-\n t/t5543-atomic-push.sh     | 92 ++++++++++++++++++++++++++++++++++++++\n transport-helper.c         | 15 +++++++\n transport.c                | 14 ------\n 5 files changed, 119 insertions(+), 16 deletions(-)\n\n-- \n2.26.0.3.ga7a9d752d4\n\n"},{"id":"393970","messageId":"20200325143608.45141-3-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"20200322131815.11872-3-worldhello.net@gmail.com","subject":"[PATCH 2/3] send-pack: mark failure of atomic push properly","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-25T14:36:07Z","receivedAt":"2020-03-25T14:36:27Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"When pusing with SSH or other smart protocol, references are validated\nby function `check_to_send_update()` beforce they are sent in commands\nto `send_pack()` of \"receve-pack\".  For atomic push, if a reference is\nrejected after the validation, only references pushed by user should be\nmarked as failure, instead of report failure on all remote references.\n\nCommit v2.22.0-1-g3bca1e7f9f (transport-helper: enforce atomic in\npush_refs_with_push, 2019-07-11) wanted to fix report issue of HTTP\nprotocol, but marked all remote references failure for atomic push.\n\nRevert part of that commit and add additional status for function\n`atomic_push_failure()`.  The additional status for it except the\n\"REF_STATUS_EXPECTING_REPORT\" status are:\n\n- REF_STATUS_NONE : Not marked as \"REF_STATUS_EXPECTING_REPORT\" yet.\n- REF_STATUS_OK   : Assume OK for dryrun or status_report is disabled.\n\nThis commit break test case in t5541, and will fix in other commit.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n send-pack.c                |  2 ++\n t/t5541-http-push-smart.sh |  2 +-\n t/t5543-atomic-push.sh     |  6 +++---\n transport.c                | 14 --------------\n 4 files changed, 6 insertions(+), 18 deletions(-)\n\ndiff --git a/send-pack.c b/send-pack.c\nindex 0407841ae8..ff016c468c 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -334,6 +334,8 @@ static int atomic_push_failure(struct send_pack_args *args,\n \t\t\tcontinue;\n \n \t\tswitch (ref->status) {\n+\t\tcase REF_STATUS_NONE:\n+\t\tcase REF_STATUS_OK:\n \t\tcase REF_STATUS_EXPECTING_REPORT:\n \t\t\tref->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n \t\t\tcontinue;\ndiff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh\nindex 23be8ce92d..2c2c3fb0f5 100755\n--- a/t/t5541-http-push-smart.sh\n+++ b/t/t5541-http-push-smart.sh\n@@ -177,7 +177,7 @@ test_expect_success 'push (chunked)' '\n \t test $HEAD = $(git rev-parse --verify HEAD))\n '\n \n-test_expect_success 'push --atomic also prevents branch creation, reports collateral' '\n+test_expect_failure 'push --atomic also prevents branch creation, reports collateral' '\n \t# Setup upstream repo - empty for now\n \td=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&\n \tgit init --bare \"$d\" &&\ndiff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh\nindex 4b4c0a262b..d7be1c098b 100755\n--- a/t/t5543-atomic-push.sh\n+++ b/t/t5543-atomic-push.sh\n@@ -201,7 +201,7 @@ test_expect_success 'atomic push is not advertised if configured' '\n # References in upstream : master(1) one(1) foo(1)\n # References in workbench: master(2)        foo(1) two(2) bar(2)\n # Atomic push            : master(2)               two(2) bar(2)\n-test_expect_failure 'atomic push reports (reject by update hook)' '\n+test_expect_success 'atomic push reports (reject by update hook)' '\n \tmk_repo_pair &&\n \t(\n \t\tcd workbench &&\n@@ -244,7 +244,7 @@ test_expect_failure 'atomic push reports (reject by update hook)' '\n \n # References in upstream : master(1) one(1) foo(1)\n # References in workbench: master(2)        foo(1) two(2) bar(2)\n-test_expect_failure 'atomic push reports (mirror, but reject by update hook)' '\n+test_expect_success 'atomic push reports (mirror, but reject by update hook)' '\n \t(\n \t\tcd workbench &&\n \t\tgit remote remove up &&\n@@ -265,7 +265,7 @@ test_expect_failure 'atomic push reports (mirror, but reject by update hook)' '\n \n # References in upstream : master(2) one(1) foo(1)\n # References in workbench: master(1)        foo(1) two(2) bar(2)\n-test_expect_failure 'atomic push reports (reject by non-ff)' '\n+test_expect_success 'atomic push reports (reject by non-ff)' '\n \trm upstream/.git/hooks/update &&\n \t(\n \t\tcd workbench &&\ndiff --git a/transport.c b/transport.c\nindex 1fdc7dac1a..75c5c9fe98 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1240,20 +1240,6 @@ int transport_push(struct repository *r,\n \t\terr = push_had_errors(remote_refs);\n \t\tret = push_ret | err;\n \n-\t\tif ((flags & TRANSPORT_PUSH_ATOMIC) && err) {\n-\t\t\tstruct ref *it;\n-\t\t\tfor (it = remote_refs; it; it = it->next)\n-\t\t\t\tswitch (it->status) {\n-\t\t\t\tcase REF_STATUS_NONE:\n-\t\t\t\tcase REF_STATUS_UPTODATE:\n-\t\t\t\tcase REF_STATUS_OK:\n-\t\t\t\t\tit->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n-\t\t\t\t\tbreak;\n-\t\t\t\tdefault:\n-\t\t\t\t\tbreak;\n-\t\t\t\t}\n-\t\t}\n-\n \t\tif (!quiet || err)\n \t\t\ttransport_print_push_status(transport->url, remote_refs,\n \t\t\t\t\tverbose | porcelain, porcelain,\n-- \n2.26.0.3.ga7a9d752d4\n\n"},{"id":"393971","messageId":"20200325143608.45141-4-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"20200322131815.11872-3-worldhello.net@gmail.com","subject":"[PATCH 3/3] transport-helper: enforce atomic in push_refs_with_push","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-25T14:36:08Z","receivedAt":"2020-03-25T14:36:31Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Commit v2.22.0-1-g3bca1e7f9f (transport-helper: enforce atomic in\npush_refs_with_push, 2019-07-11) noticed the incomplete report of\nfailure of an atomic push for HTTP protocol.  But the implementation\nhas a flaw that mark all remote references as failure.\n\nOnly mark necessary references as failure in `push_refs_with_push()` of\ntransport-helper.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5541-http-push-smart.sh | 14 +++++++++++---\n transport-helper.c         | 15 +++++++++++++++\n 2 files changed, 26 insertions(+), 3 deletions(-)\n\ndiff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh\nindex 2c2c3fb0f5..afc680d5e3 100755\n--- a/t/t5541-http-push-smart.sh\n+++ b/t/t5541-http-push-smart.sh\n@@ -177,7 +177,10 @@ test_expect_success 'push (chunked)' '\n \t test $HEAD = $(git rev-parse --verify HEAD))\n '\n \n-test_expect_failure 'push --atomic also prevents branch creation, reports collateral' '\n+## References of remote: atomic1(1)            master(2) collateral(2) other(2)\n+## References of local :            atomic2(2) master(1) collateral(3) other(2) collateral1(3) atomic(1)\n+## Atomic push         :                       master(1) collateral(3)                         atomic(1)\n+test_expect_success 'push --atomic also prevents branch creation, reports collateral' '\n \t# Setup upstream repo - empty for now\n \td=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&\n \tgit init --bare \"$d\" &&\n@@ -189,7 +192,8 @@ test_expect_failure 'push --atomic also prevents branch creation, reports collat\n \ttest_commit atomic2 &&\n \tgit branch collateral &&\n \tgit branch other &&\n-\tgit push \"$up\" master collateral other &&\n+\tgit push \"$up\" atomic1 master collateral other &&\n+\tgit tag -d atomic1 &&\n \n \t# collateral is a valid push, but should be failed by atomic push\n \tgit checkout collateral &&\n@@ -224,7 +228,11 @@ test_expect_failure 'push --atomic also prevents branch creation, reports collat\n \n \t# the collateral failure refs should be indicated to the user\n \tgrep \"^ ! .*rejected.* atomic -> atomic .*atomic push failed\" output &&\n-\tgrep \"^ ! .*rejected.* collateral -> collateral .*atomic push failed\" output\n+\tgrep \"^ ! .*rejected.* collateral -> collateral .*atomic push failed\" output &&\n+\n+\t# never report what we do not push\n+\t! grep \"^ ! .*rejected.* atomic1 \" output &&\n+\t! grep \"^ ! .*rejected.* other \" output\n '\n \n test_expect_success 'push --atomic fails on server-side errors' '\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 20a7185ec4..ab3b52eb14 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -894,6 +894,21 @@ static int push_refs_with_push(struct transport *transport,\n \t\tcase REF_STATUS_REJECT_STALE:\n \t\tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n \t\t\tif (atomic) {\n+\t\t\t\t/* Mark other refs as failed */\n+\t\t\t\tfor (ref = remote_refs; ref; ref = ref->next) {\n+\t\t\t\t\tif (!ref->peer_ref && !mirror)\n+\t\t\t\t\t\tcontinue;\n+\n+\t\t\t\t\tswitch (ref->status) {\n+\t\t\t\t\tcase REF_STATUS_NONE:\n+\t\t\t\t\tcase REF_STATUS_OK:\n+\t\t\t\t\tcase REF_STATUS_EXPECTING_REPORT:\n+\t\t\t\t\t\tref->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\tdefault:\n+\t\t\t\t\t\tbreak; /* do nothing */\n+\t\t\t\t\t}\n+\t\t\t\t}\n \t\t\t\tstring_list_clear(&cas_options, 0);\n \t\t\t\treturn 0;\n \t\t\t} else\n-- \n2.26.0.3.ga7a9d752d4\n\n"},{"id":"393972","messageId":"xmqqa744xyoo.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200325143608.45141-2-zhiyou.jx@alibaba-inc.com","subject":"Re: [PATCH 1/3] t5543: never report what we do not push","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-03-25T15:05:11Z","receivedAt":"2020-03-25T15:05:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> +format_git_output () {\n\nUnless this helper is able to take any git output and massage,\nplease describe what kind of git output it is meant to handle.\n\nAlso, \"format\" does not tell anything to the readers why it wants to\ntransform its input or what its output is supposed to look like.  It\ndoes not help readers and future developers.\n\n> +\tawk '/^(To| !) / {print}' | \\\n> +\tsed \\\n> +\t\t-e \"s/  *\\$//g\" \\\n\nWhat's the point of /g?  You are anchoring the pattern (i.e. one or\nmore SP) to the right end of the line, so it's not like it would\ntransform \"a  b c   \" into \"abc\".  Also it would be sufficient to\nsay \"zero or more\" and that would be shorter, I think.\n\n> +\t\t-e \"s/'/\\\"/g\"\n\nIt is unclear what this thing is for.  If the output from a git\nsubcommand you are munging with the helper says <don't>, this will\nturn it into <don\"t>, presumably before comparing it with the\nexpected output you'd literally prepare in the test script.  Are the\ngit subcommands whose output you are munging unstable and sometimes\nuse single and sometimes use double quotes?  \n\nIf not, if you used single quotes when preparing the expected\noutput, wouldn't you be able to do without this?\n\nIs it because you'd have the code that prepares the expected output\ninside a sq pair (because it is in test_expect_thing), and it is\ncumbersome to write a literal single quote?  If that is the reason,\nthat is understandable, but I think readers deserve some comments\nexplaining why these transformations are done.  Please do not waste\nreaders' time.\n\nIt looks wasteful to pipe awk output to sed.  I wonder if something\nalong the lines of\n\n\tsed -ne \"/^\\(To\\| !\\) /{\n\t\ts/ *\\$//\n\t\ts/'/\\\"/g\n\t\tp\n\t}\"\n\nwould do the same thing with a single tool.\n\n> +# References in upstream : master(1) one(1) foo(1)\n> +# References in workbench: master(2)        foo(1) two(2) bar(2)\n> +# Atomic push            : master(2)               two(2) bar(2)\n> +test_expect_failure 'atomic push reports (reject by update hook)' '\n> +\tmk_repo_pair &&\n> +\t(\n> +\t\tcd workbench &&\n> +\t\t# Keep constant output.\n> +\t\tgit config core.abbrev 7 &&\n\nThis '7' is \"use at least seven hexdigits\", so it does not give any\nguarantee that your output will be stable.  Depending on chance,\nsome objects may be shown using 8 or more---is our \"munge output\nbefore comparison\" helper prepared for such a case?\n\n> +\t\ttest_commit one &&\n> +\t\tgit branch foo &&\n> +\t\tgit push up master one foo &&\n> +\t\tgit tag -d one\n> +\t) &&\n> +\t(\n> +\t\tmkdir -p upstream/.git/hooks &&\n> +\t\tcat >upstream/.git/hooks/update <<-EOF &&\n> +\t\t#!/bin/sh\n> +\n> +\t\tif test \"\\$1\" = \"refs/heads/bar\"\n> +\t\tthen\n> +\t\t\techo >2 \"Pusing to branch bar is prohibited\"\n\nMeant to redirect to the standard error stream, not a file \"2\"?\n\n> +\t\t\texit 1\n> +\t\tfi\n> +\t\tEOF\n> +\t\tchmod a+x upstream/.git/hooks/update\n> +\t) &&\n> +\t(\n> +\t\tcd workbench &&\n> +\t\ttest_commit two &&\n> +\t\tgit branch bar\n> +\t) &&\n\nAt this point, we have the refs described in the comment at the\nbeginning (which is greatly appreciated---it hels understanding what\nis going on).\n\n> +\ttest_must_fail git -C workbench \\\n> +\t\tpush --atomic up master two bar >out 2>&1 &&\n\nAs \"git push\" does not auto-follow tags, what we push will be\nexactly these three refs.\n\n> +\tformat_git_output <out >actual &&\n> +\tcat >expect <<-EOF &&\n> +\tTo ../upstream\n> +\t ! [remote rejected] master -> master (atomic push failure)\n> +\t ! [remote rejected] two -> two (atomic push failure)\n> +\t ! [remote rejected] bar -> bar (hook declined)\n> +\tEOF\n\nAnd we expect them to be rejected, as 'bar' needs to stay constant\nthere.  \n\n> +\ttest_cmp expect actual\n\nAll makes sense.\n\n"},{"id":"393982","messageId":"xmqq5zesxy6y.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200325143608.45141-3-zhiyou.jx@alibaba-inc.com","subject":"Re: [PATCH 2/3] send-pack: mark failure of atomic push properly","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-03-25T15:15:49Z","receivedAt":"2020-03-25T15:15:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> When pusing with SSH or other smart protocol, references are validated\n> by function `check_to_send_update()` beforce they are sent in commands\n\nbeforce -> before\n\n> to `send_pack()` of \"receve-pack\".  For atomic push, if a reference is\n> rejected after the validation, only references pushed by user should be\n> marked as failure, instead of report failure on all remote references.\n>\n> Commit v2.22.0-1-g3bca1e7f9f (transport-helper: enforce atomic in\n> push_refs_with_push, 2019-07-11) wanted to fix report issue of HTTP\n> protocol, but marked all remote references failure for atomic push.\n>\n> Revert part of that commit and add additional status for function\n> `atomic_push_failure()`.  The additional status for it except the\n> \"REF_STATUS_EXPECTING_REPORT\" status are:\n>\n> - REF_STATUS_NONE : Not marked as \"REF_STATUS_EXPECTING_REPORT\" yet.\n> - REF_STATUS_OK   : Assume OK for dryrun or status_report is disabled.\n>\n> This commit break test case in t5541, and will fix in other commit.\n\nPresumably the fix in 3/3 is too big to be included in here?  Let's\nsee how it goes, as such a strategy sometimes helps understand the\nchange and sometimes does not.\n\n> diff --git a/send-pack.c b/send-pack.c\n> index 0407841ae8..ff016c468c 100644\n> --- a/send-pack.c\n> +++ b/send-pack.c\n> @@ -334,6 +334,8 @@ static int atomic_push_failure(struct send_pack_args *args,\n>  \t\t\tcontinue;\n>  \n>  \t\tswitch (ref->status) {\n> +\t\tcase REF_STATUS_NONE:\n> +\t\tcase REF_STATUS_OK:\n>  \t\tcase REF_STATUS_EXPECTING_REPORT:\n>  \t\t\tref->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n>  \t\t\tcontinue;\n\nThis is fed the whole *remote_refs list, iterates over them but we\nonly come here if the ref is actually pushed (i.e. peer_ref is set,\nor we are doing a mirror push), so it does sound like the right\nplace to do this.\n"},{"id":"393983","messageId":"xmqq1rpgxxfm.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200325143608.45141-4-zhiyou.jx@alibaba-inc.com","subject":"Re: [PATCH 3/3] transport-helper: enforce atomic in push_refs_with_push","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-03-25T15:32:13Z","receivedAt":"2020-03-25T15:32:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> diff --git a/transport-helper.c b/transport-helper.c\n> index 20a7185ec4..ab3b52eb14 100644\n> --- a/transport-helper.c\n> +++ b/transport-helper.c\n> @@ -894,6 +894,21 @@ static int push_refs_with_push(struct transport *transport,\n>  \t\tcase REF_STATUS_REJECT_STALE:\n>  \t\tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n>  \t\t\tif (atomic) {\n> +\t\t\t\t/* Mark other refs as failed */\n> +\t\t\t\tfor (ref = remote_refs; ref; ref = ref->next) {\n> +\t\t\t\t\tif (!ref->peer_ref && !mirror)\n> +\t\t\t\t\t\tcontinue;\n> +\n> +\t\t\t\t\tswitch (ref->status) {\n> +\t\t\t\t\tcase REF_STATUS_NONE:\n> +\t\t\t\t\tcase REF_STATUS_OK:\n> +\t\t\t\t\tcase REF_STATUS_EXPECTING_REPORT:\n> +\t\t\t\t\t\tref->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n> +\t\t\t\t\t\tcontinue;\n> +\t\t\t\t\tdefault:\n> +\t\t\t\t\t\tbreak; /* do nothing */\n> +\t\t\t\t\t}\n> +\t\t\t\t}\n\nOK, so this is more in line with the check done in send_pack() that\nfails the push before we even send any pack data.  I wonder if it is\nworth considering to move the logic of this loop into a helper\nfunction so that this and the other one in 2/3 can call it and stay\nin sync, something along the lines of\n\n\tvoid reject_push_to_other_refs(struct ref *ref, int mirror_mode)\n\t{\n\t\tfor (; ref; ref = ref->next) {\n\t\t\t... one iteration of the above loop ...\n\t\t}\n\t}\n\nThen the above part would become\n\n\t\tcase REF_STATUS_REJECT_FOO:\n\t\t\tif (atomic)\n\t\t\t\treject_push_to_other_refs(remote_refs, mirror);\n\nand the part modified by 2/3 would also become\n\n\tstatic int atomic_push_failure(...)\n\t{\n\t\treject_push_to_other_refs(remote_refs, args->send_mirror);\n\t\treturn error(\"atomic push failed ...\");\n\t}\n\nI am not sure if it is better to keep 2/3 and 3/3 separate or make\nthem into a single step, but perhaps it is because I am not getting\nthe true reason why you made them separate in the first place.\n\nThanks.\n"},{"id":"394040","messageId":"CANYiYbEQPUu5OyrGkmM7SVuX-edffn_5n5ywXv6D2Xh0=O5raA@mail.gmail.com","threadId":"52936","inReplyTo":"xmqqa744xyoo.fsf@gitster.c.googlers.com","subject":"Re: [PATCH 1/3] t5543: never report what we do not push","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-26T02:25:30Z","receivedAt":"2020-03-26T02:25:45Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2020年3月25日周三 下午11:05写道：\n>\n> Jiang Xin <worldhello.net@gmail.com> writes:\n>\n> > +format_git_output () {\n>\n> Unless this helper is able to take any git output and massage,\n> please describe what kind of git output it is meant to handle.\n>\nWil use \"cut_status_report_and_tidy\" as the function name.\n\n> Also, \"format\" does not tell anything to the readers why it wants to\n> transform its input or what its output is supposed to look like.  It\n> does not help readers and future developers.\n>\n> > +     awk '/^(To| !) / {print}' | \\\n> > +     sed \\\n> > +             -e \"s/  *\\$//g\" \\\n>\n> What's the point of /g?  You are anchoring the pattern (i.e. one or\n> more SP) to the right end of the line, so it's not like it would\n> transform \"a  b c   \" into \"abc\".  Also it would be sufficient to\n> say \"zero or more\" and that would be shorter, I think.\n\nI want to remove the trailing spaces in git output.  I don't know why\nthere are trailing spaces in each \"remote: \" message.\nBut there is not trailing spaces in status report message, so I will\nremote it in next reroll.\n\n>\n> > +             -e \"s/'/\\\"/g\"\n>\n> It is unclear what this thing is for.  If the output from a git\n> subcommand you are munging with the helper says <don't>, this will\n> turn it into <don\"t>, presumably before comparing it with the\n> expected output you'd literally prepare in the test script.  Are the\n> git subcommands whose output you are munging unstable and sometimes\n> use single and sometimes use double quotes?\n>\n> If not, if you used single quotes when preparing the expected\n> output, wouldn't you be able to do without this?\n>\n> Is it because you'd have the code that prepares the expected output\n> inside a sq pair (because it is in test_expect_thing), and it is\n> cumbersome to write a literal single quote?  If that is the reason,\n> that is understandable, but I think readers deserve some comments\n> explaining why these transformations are done.  Please do not waste\n> readers' time.\n\nTo prepare expect message, sometime I have to escape the single quote like this\n\n    cat >expect <<-EOF\n        error: failed to push some refs to '\"'\"'../upstream'\"'\"'\n    EOF\n\nIt's boring to type '\"'\"' instead of a double quote.\n\n>\n> It looks wasteful to pipe awk output to sed.  I wonder if something\n> along the lines of\n>\n>         sed -ne \"/^\\(To\\| !\\) /{\n>                 s/ *\\$//\n>                 s/'/\\\"/g\n>                 p\n>         }\"\n>\n> would do the same thing with a single tool.\n\nThat's better.\n\n> > +# References in upstream : master(1) one(1) foo(1)\n> > +# References in workbench: master(2)        foo(1) two(2) bar(2)\n> > +# Atomic push            : master(2)               two(2) bar(2)\n> > +test_expect_failure 'atomic push reports (reject by update hook)' '\n> > +     mk_repo_pair &&\n> > +     (\n> > +             cd workbench &&\n> > +             # Keep constant output.\n> > +             git config core.abbrev 7 &&\n>\n> This '7' is \"use at least seven hexdigits\", so it does not give any\n> guarantee that your output will be stable.  Depending on chance,\n> some objects may be shown using 8 or more---is our \"munge output\n> before comparison\" helper prepared for such a case?\n\nWill use sed -e 's/   */ /g' on output message to make it stable.\n\n>\n> > +             test_commit one &&\n> > +             git branch foo &&\n> > +             git push up master one foo &&\n> > +             git tag -d one\n> > +     ) &&\n> > +     (\n> > +             mkdir -p upstream/.git/hooks &&\n> > +             cat >upstream/.git/hooks/update <<-EOF &&\n> > +             #!/bin/sh\n> > +\n> > +             if test \"\\$1\" = \"refs/heads/bar\"\n> > +             then\n> > +                     echo >2 \"Pusing to branch bar is prohibited\"\n>\n> Meant to redirect to the standard error stream, not a file \"2\"?\n\nIt's a typo, should be \"echo >&2\".\n"},{"id":"394278","messageId":"20200329143329.13846-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200325143608.45141-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v2 0/4] Never report references we not push","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-29T14:33:25Z","receivedAt":"2020-03-29T14:33:38Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nHi,\n\nPatches 2/4, 4/3 and 4/4 can be squashed into one commit.\n\n## Changes since v1:\n\n* Add a new common method `reject_atomic_push()`, which is reused for \n  atomic push rejection for SSH and HTTP protocol.\n\n* Other changes see the attached range diff.\n\n\n## Range diff v1..v2\n\n1:  e1c7aa3501 ! 1:  d84ebf30b5 t5543: never report what we do not push\n    @@ t/t5543-atomic-push.sh: test_refs () {\n      \ttest_cmp expect actual\n      }\n      \n    -+format_git_output () {\n    -+\tawk '/^(To| !) / {print}' | \\\n    -+\tsed \\\n    -+\t\t-e \"s/  *\\$//g\" \\\n    -+\t\t-e \"s/'/\\\"/g\"\n    ++fmt_status_report () {\n    ++\tsed -n \\\n    ++\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n    ++\t\t-e \"/^ ! / { s/   */ /g; p; }\"\n     +}\n     +\n      test_expect_success 'atomic push works for a single branch' '\n    @@ t/t5543-atomic-push.sh: test_expect_success 'atomic push is not advertised if co\n     +\tmk_repo_pair &&\n     +\t(\n     +\t\tcd workbench &&\n    -+\t\t# Keep constant output.\n    -+\t\tgit config core.abbrev 7 &&\n     +\t\ttest_commit one &&\n     +\t\tgit branch foo &&\n     +\t\tgit push up master one foo &&\n    @@ t/t5543-atomic-push.sh: test_expect_success 'atomic push is not advertised if co\n     +\n     +\t\tif test \"\\$1\" = \"refs/heads/bar\"\n     +\t\tthen\n    -+\t\t\techo >2 \"Pusing to branch bar is prohibited\"\n    ++\t\t\techo >&2 \"Pusing to branch bar is prohibited\"\n     +\t\t\texit 1\n     +\t\tfi\n     +\t\tEOF\n    @@ t/t5543-atomic-push.sh: test_expect_success 'atomic push is not advertised if co\n     +\t) &&\n     +\ttest_must_fail git -C workbench \\\n     +\t\tpush --atomic up master two bar >out 2>&1 &&\n    -+\tformat_git_output <out >actual &&\n    ++\tfmt_status_report <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tTo ../upstream\n     +\t ! [remote rejected] master -> master (atomic push failure)\n    @@ t/t5543-atomic-push.sh: test_expect_success 'atomic push is not advertised if co\n     +\t) &&\n     +\ttest_must_fail git -C workbench \\\n     +\t\tpush --atomic --mirror up >out 2>&1 &&\n    -+\tformat_git_output <out >actual &&\n    ++\tfmt_status_report <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tTo ../upstream\n     +\t ! [remote rejected] master -> master (atomic push failure)\n    @@ t/t5543-atomic-push.sh: test_expect_success 'atomic push is not advertised if co\n     +\t) &&\n     +\ttest_must_fail git -C workbench \\\n     +\t\tpush --atomic up master foo bar >out 2>&1 &&\n    -+\tformat_git_output <out >actual &&\n    ++\tfmt_status_report <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tTo ../upstream\n    -+\t ! [rejected]        master -> master (non-fast-forward)\n    -+\t ! [rejected]        bar -> bar (atomic push failed)\n    ++\t ! [rejected] master -> master (non-fast-forward)\n    ++\t ! [rejected] bar -> bar (atomic push failed)\n     +\tEOF\n     +\ttest_cmp expect actual\n     +'\n2:  50f1e02a2f ! 2:  a76340633b send-pack: mark failure of atomic push properly\n    @@ Metadata\n      ## Commit message ##\n         send-pack: mark failure of atomic push properly\n     \n    -    When pusing with SSH or other smart protocol, references are validated\n    -    by function `check_to_send_update()` beforce they are sent in commands\n    +    When pushing with SSH or other smart protocol, references are validated\n    +    by function `check_to_send_update()` before they are sent in commands\n         to `send_pack()` of \"receve-pack\".  For atomic push, if a reference is\n         rejected after the validation, only references pushed by user should be\n         marked as failure, instead of report failure on all remote references.\n    @@ Commit message\n         push_refs_with_push, 2019-07-11) wanted to fix report issue of HTTP\n         protocol, but marked all remote references failure for atomic push.\n     \n    -    Revert part of that commit and add additional status for function\n    -    `atomic_push_failure()`.  The additional status for it except the\n    -    \"REF_STATUS_EXPECTING_REPORT\" status are:\n    +    In order to fix the issue of status report for SSH or other built-in\n    +    smart protocol, revert part of that commit and add additional status\n    +    for function `atomic_push_failure()`.  The additional status for it\n    +    except the \"REF_STATUS_EXPECTING_REPORT\" status are:\n     \n         - REF_STATUS_NONE : Not marked as \"REF_STATUS_EXPECTING_REPORT\" yet.\n         - REF_STATUS_OK   : Assume OK for dryrun or status_report is disabled.\n     \n    -    This commit break test case in t5541, and will fix in other commit.\n    +    This fix won't resolve the issue of status report in transport-helper\n    +    for HTTP or other protocols, and breaks test case in t5541.  Will fix\n    +    it in additional commit.\n     \n         Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n     \n3:  50bb31ed3a ! 3:  82a8b19430 transport-helper: enforce atomic in push_refs_with_push\n    @@ Metadata\n     Author: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n     \n      ## Commit message ##\n    -    transport-helper: enforce atomic in push_refs_with_push\n    +    transport-helper: mark failure for atomic push\n     \n         Commit v2.22.0-1-g3bca1e7f9f (transport-helper: enforce atomic in\n         push_refs_with_push, 2019-07-11) noticed the incomplete report of\n-:  ---------- > 4:  fc8c1af33c transport-helper: new method reject_atomic_push()\n\n\nJiang Xin (4):\n  t5543: never report what we do not push\n  send-pack: mark failure of atomic push properly\n  transport-helper: mark failure for atomic push\n  transport-helper: new method reject_atomic_push()\n\n send-pack.c                | 27 ++----------\n t/t5541-http-push-smart.sh | 12 ++++-\n t/t5543-atomic-push.sh     | 89 ++++++++++++++++++++++++++++++++++++++\n transport-helper.c         | 23 ++++++++++\n transport.c                | 14 ------\n transport.h                |  3 ++\n 6 files changed, 128 insertions(+), 40 deletions(-)\n\n-- \n2.26.0.4.g39bcdcb101.dirty\n\n"},{"id":"394279","messageId":"20200329143329.13846-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200325143608.45141-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v2 1/4] t5543: never report what we do not push","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-29T14:33:26Z","receivedAt":"2020-03-29T14:33:42Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen we push some references to the git server, we expect git to report\nthe status of the references we are pushing; no more, no less.  But when\npusing with atomic mode, if some references cannot be pushed, Git reports\nthe reject message on all references in the remote repository.\n\nAdd new test cases in t5543, and fix them in latter commit.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5543-atomic-push.sh | 89 ++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 89 insertions(+)\n\ndiff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh\nindex 7079bcf9a0..001240eec7 100755\n--- a/t/t5543-atomic-push.sh\n+++ b/t/t5543-atomic-push.sh\n@@ -27,6 +27,12 @@ test_refs () {\n \ttest_cmp expect actual\n }\n \n+fmt_status_report () {\n+\tsed -n \\\n+\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n+\t\t-e \"/^ ! / { s/   */ /g; p; }\"\n+}\n+\n test_expect_success 'atomic push works for a single branch' '\n \tmk_repo_pair &&\n \t(\n@@ -191,4 +197,87 @@ test_expect_success 'atomic push is not advertised if configured' '\n \ttest_refs master HEAD@{1}\n '\n \n+# References in upstream : master(1) one(1) foo(1)\n+# References in workbench: master(2)        foo(1) two(2) bar(2)\n+# Atomic push            : master(2)               two(2) bar(2)\n+test_expect_failure 'atomic push reports (reject by update hook)' '\n+\tmk_repo_pair &&\n+\t(\n+\t\tcd workbench &&\n+\t\ttest_commit one &&\n+\t\tgit branch foo &&\n+\t\tgit push up master one foo &&\n+\t\tgit tag -d one\n+\t) &&\n+\t(\n+\t\tmkdir -p upstream/.git/hooks &&\n+\t\tcat >upstream/.git/hooks/update <<-EOF &&\n+\t\t#!/bin/sh\n+\n+\t\tif test \"\\$1\" = \"refs/heads/bar\"\n+\t\tthen\n+\t\t\techo >&2 \"Pusing to branch bar is prohibited\"\n+\t\t\texit 1\n+\t\tfi\n+\t\tEOF\n+\t\tchmod a+x upstream/.git/hooks/update\n+\t) &&\n+\t(\n+\t\tcd workbench &&\n+\t\ttest_commit two &&\n+\t\tgit branch bar\n+\t) &&\n+\ttest_must_fail git -C workbench \\\n+\t\tpush --atomic up master two bar >out 2>&1 &&\n+\tfmt_status_report <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo ../upstream\n+\t ! [remote rejected] master -> master (atomic push failure)\n+\t ! [remote rejected] two -> two (atomic push failure)\n+\t ! [remote rejected] bar -> bar (hook declined)\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# References in upstream : master(1) one(1) foo(1)\n+# References in workbench: master(2)        foo(1) two(2) bar(2)\n+test_expect_failure 'atomic push reports (mirror, but reject by update hook)' '\n+\t(\n+\t\tcd workbench &&\n+\t\tgit remote remove up &&\n+\t\tgit remote add up ../upstream\n+\t) &&\n+\ttest_must_fail git -C workbench \\\n+\t\tpush --atomic --mirror up >out 2>&1 &&\n+\tfmt_status_report <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo ../upstream\n+\t ! [remote rejected] master -> master (atomic push failure)\n+\t ! [remote rejected] one (atomic push failure)\n+\t ! [remote rejected] bar -> bar (hook declined)\n+\t ! [remote rejected] two -> two (atomic push failure)\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# References in upstream : master(2) one(1) foo(1)\n+# References in workbench: master(1)        foo(1) two(2) bar(2)\n+test_expect_failure 'atomic push reports (reject by non-ff)' '\n+\trm upstream/.git/hooks/update &&\n+\t(\n+\t\tcd workbench &&\n+\t\tgit push up master &&\n+\t\tgit reset --hard HEAD^\n+\t) &&\n+\ttest_must_fail git -C workbench \\\n+\t\tpush --atomic up master foo bar >out 2>&1 &&\n+\tfmt_status_report <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo ../upstream\n+\t ! [rejected] master -> master (non-fast-forward)\n+\t ! [rejected] bar -> bar (atomic push failed)\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.26.0.4.g39bcdcb101.dirty\n\n"},{"id":"394280","messageId":"20200329143329.13846-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200325143608.45141-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v2 2/4] send-pack: mark failure of atomic push properly","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-29T14:33:27Z","receivedAt":"2020-03-29T14:33:42Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing with SSH or other smart protocol, references are validated\nby function `check_to_send_update()` before they are sent in commands\nto `send_pack()` of \"receve-pack\".  For atomic push, if a reference is\nrejected after the validation, only references pushed by user should be\nmarked as failure, instead of report failure on all remote references.\n\nCommit v2.22.0-1-g3bca1e7f9f (transport-helper: enforce atomic in\npush_refs_with_push, 2019-07-11) wanted to fix report issue of HTTP\nprotocol, but marked all remote references failure for atomic push.\n\nIn order to fix the issue of status report for SSH or other built-in\nsmart protocol, revert part of that commit and add additional status\nfor function `atomic_push_failure()`.  The additional status for it\nexcept the \"REF_STATUS_EXPECTING_REPORT\" status are:\n\n- REF_STATUS_NONE : Not marked as \"REF_STATUS_EXPECTING_REPORT\" yet.\n- REF_STATUS_OK   : Assume OK for dryrun or status_report is disabled.\n\nThis fix won't resolve the issue of status report in transport-helper\nfor HTTP or other protocols, and breaks test case in t5541.  Will fix\nit in additional commit.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n send-pack.c                |  2 ++\n t/t5541-http-push-smart.sh |  2 +-\n t/t5543-atomic-push.sh     |  6 +++---\n transport.c                | 14 --------------\n 4 files changed, 6 insertions(+), 18 deletions(-)\n\ndiff --git a/send-pack.c b/send-pack.c\nindex 0407841ae8..ff016c468c 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -334,6 +334,8 @@ static int atomic_push_failure(struct send_pack_args *args,\n \t\t\tcontinue;\n \n \t\tswitch (ref->status) {\n+\t\tcase REF_STATUS_NONE:\n+\t\tcase REF_STATUS_OK:\n \t\tcase REF_STATUS_EXPECTING_REPORT:\n \t\t\tref->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n \t\t\tcontinue;\ndiff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh\nindex 23be8ce92d..2c2c3fb0f5 100755\n--- a/t/t5541-http-push-smart.sh\n+++ b/t/t5541-http-push-smart.sh\n@@ -177,7 +177,7 @@ test_expect_success 'push (chunked)' '\n \t test $HEAD = $(git rev-parse --verify HEAD))\n '\n \n-test_expect_success 'push --atomic also prevents branch creation, reports collateral' '\n+test_expect_failure 'push --atomic also prevents branch creation, reports collateral' '\n \t# Setup upstream repo - empty for now\n \td=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&\n \tgit init --bare \"$d\" &&\ndiff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh\nindex 001240eec7..620c30d58f 100755\n--- a/t/t5543-atomic-push.sh\n+++ b/t/t5543-atomic-push.sh\n@@ -200,7 +200,7 @@ test_expect_success 'atomic push is not advertised if configured' '\n # References in upstream : master(1) one(1) foo(1)\n # References in workbench: master(2)        foo(1) two(2) bar(2)\n # Atomic push            : master(2)               two(2) bar(2)\n-test_expect_failure 'atomic push reports (reject by update hook)' '\n+test_expect_success 'atomic push reports (reject by update hook)' '\n \tmk_repo_pair &&\n \t(\n \t\tcd workbench &&\n@@ -241,7 +241,7 @@ test_expect_failure 'atomic push reports (reject by update hook)' '\n \n # References in upstream : master(1) one(1) foo(1)\n # References in workbench: master(2)        foo(1) two(2) bar(2)\n-test_expect_failure 'atomic push reports (mirror, but reject by update hook)' '\n+test_expect_success 'atomic push reports (mirror, but reject by update hook)' '\n \t(\n \t\tcd workbench &&\n \t\tgit remote remove up &&\n@@ -262,7 +262,7 @@ test_expect_failure 'atomic push reports (mirror, but reject by update hook)' '\n \n # References in upstream : master(2) one(1) foo(1)\n # References in workbench: master(1)        foo(1) two(2) bar(2)\n-test_expect_failure 'atomic push reports (reject by non-ff)' '\n+test_expect_success 'atomic push reports (reject by non-ff)' '\n \trm upstream/.git/hooks/update &&\n \t(\n \t\tcd workbench &&\ndiff --git a/transport.c b/transport.c\nindex 1fdc7dac1a..75c5c9fe98 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1240,20 +1240,6 @@ int transport_push(struct repository *r,\n \t\terr = push_had_errors(remote_refs);\n \t\tret = push_ret | err;\n \n-\t\tif ((flags & TRANSPORT_PUSH_ATOMIC) && err) {\n-\t\t\tstruct ref *it;\n-\t\t\tfor (it = remote_refs; it; it = it->next)\n-\t\t\t\tswitch (it->status) {\n-\t\t\t\tcase REF_STATUS_NONE:\n-\t\t\t\tcase REF_STATUS_UPTODATE:\n-\t\t\t\tcase REF_STATUS_OK:\n-\t\t\t\t\tit->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n-\t\t\t\t\tbreak;\n-\t\t\t\tdefault:\n-\t\t\t\t\tbreak;\n-\t\t\t\t}\n-\t\t}\n-\n \t\tif (!quiet || err)\n \t\t\ttransport_print_push_status(transport->url, remote_refs,\n \t\t\t\t\tverbose | porcelain, porcelain,\n-- \n2.26.0.4.g39bcdcb101.dirty\n\n"},{"id":"394281","messageId":"20200329143329.13846-5-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200325143608.45141-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v2 4/4] transport-helper: new method reject_atomic_push()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-29T14:33:29Z","receivedAt":"2020-03-29T14:33:43Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd new method in transport-helper to reject all references if any\nreference is failed for atomic push.\n\nThis method is reused in \"send-pack.c\" and \"transport-helper.c\", one for\nSSH, git and file protocols, and the other for HTTP protocol.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n send-pack.c        | 29 +++--------------------------\n transport-helper.c | 38 +++++++++++++++++++++++---------------\n transport.h        |  3 +++\n 3 files changed, 29 insertions(+), 41 deletions(-)\n\ndiff --git a/send-pack.c b/send-pack.c\nindex ff016c468c..a2b8eaad0d 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -322,31 +322,6 @@ static int generate_push_cert(struct strbuf *req_buf,\n \treturn update_seen;\n }\n \n-\n-static int atomic_push_failure(struct send_pack_args *args,\n-\t\t\t       struct ref *remote_refs,\n-\t\t\t       struct ref *failing_ref)\n-{\n-\tstruct ref *ref;\n-\t/* Mark other refs as failed */\n-\tfor (ref = remote_refs; ref; ref = ref->next) {\n-\t\tif (!ref->peer_ref && !args->send_mirror)\n-\t\t\tcontinue;\n-\n-\t\tswitch (ref->status) {\n-\t\tcase REF_STATUS_NONE:\n-\t\tcase REF_STATUS_OK:\n-\t\tcase REF_STATUS_EXPECTING_REPORT:\n-\t\t\tref->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n-\t\t\tcontinue;\n-\t\tdefault:\n-\t\t\tbreak; /* do nothing */\n-\t\t}\n-\t}\n-\treturn error(\"atomic push failed for ref %s. status: %d\\n\",\n-\t\t     failing_ref->name, failing_ref->status);\n-}\n-\n #define NONCE_LEN_LIMIT 256\n \n static void reject_invalid_nonce(const char *nonce, int len)\n@@ -491,7 +466,9 @@ int send_pack(struct send_pack_args *args,\n \t\t\tif (use_atomic) {\n \t\t\t\tstrbuf_release(&req_buf);\n \t\t\t\tstrbuf_release(&cap_buf);\n-\t\t\t\treturn atomic_push_failure(args, remote_refs, ref);\n+\t\t\t\treject_atomic_push(remote_refs, args->send_mirror);\n+\t\t\t\treturn error(\"atomic push failed for ref %s. status: %d\\n\",\n+\t\t\t\t\t     ref->name, ref->status);\n \t\t\t}\n \t\t\t/* else fallthrough */\n \t\tdefault:\ndiff --git a/transport-helper.c b/transport-helper.c\nindex ab3b52eb14..a46afcb69d 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -894,21 +894,7 @@ static int push_refs_with_push(struct transport *transport,\n \t\tcase REF_STATUS_REJECT_STALE:\n \t\tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n \t\t\tif (atomic) {\n-\t\t\t\t/* Mark other refs as failed */\n-\t\t\t\tfor (ref = remote_refs; ref; ref = ref->next) {\n-\t\t\t\t\tif (!ref->peer_ref && !mirror)\n-\t\t\t\t\t\tcontinue;\n-\n-\t\t\t\t\tswitch (ref->status) {\n-\t\t\t\t\tcase REF_STATUS_NONE:\n-\t\t\t\t\tcase REF_STATUS_OK:\n-\t\t\t\t\tcase REF_STATUS_EXPECTING_REPORT:\n-\t\t\t\t\t\tref->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n-\t\t\t\t\t\tcontinue;\n-\t\t\t\t\tdefault:\n-\t\t\t\t\t\tbreak; /* do nothing */\n-\t\t\t\t\t}\n-\t\t\t\t}\n+\t\t\t\treject_atomic_push(remote_refs, mirror);\n \t\t\t\tstring_list_clear(&cas_options, 0);\n \t\t\t\treturn 0;\n \t\t\t} else\n@@ -1503,3 +1489,25 @@ int bidirectional_transfer_loop(int input, int output)\n \n \treturn tloop_spawnwait_tasks(&state);\n }\n+\n+void reject_atomic_push(struct ref *remote_refs, int mirror_mode)\n+{\n+\tstruct ref *ref;\n+\n+\t/* Mark other refs as failed */\n+\tfor (ref = remote_refs; ref; ref = ref->next) {\n+\t\tif (!ref->peer_ref && !mirror_mode)\n+\t\t\tcontinue;\n+\n+\t\tswitch (ref->status) {\n+\t\tcase REF_STATUS_NONE:\n+\t\tcase REF_STATUS_OK:\n+\t\tcase REF_STATUS_EXPECTING_REPORT:\n+\t\t\tref->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n+\t\t\tcontinue;\n+\t\tdefault:\n+\t\t\tbreak; /* do nothing */\n+\t\t}\n+\t}\n+\treturn;\n+}\ndiff --git a/transport.h b/transport.h\nindex e0131daab9..4298c855be 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -265,4 +265,7 @@ int transport_refs_pushed(struct ref *ref);\n void transport_print_push_status(const char *dest, struct ref *refs,\n \t\t  int verbose, int porcelain, unsigned int *reject_reasons);\n \n+/* common method used by transport-helper.c and send-pack.c */\n+void reject_atomic_push(struct ref *refs, int mirror_mode);\n+\n #endif\n-- \n2.26.0.4.g39bcdcb101.dirty\n\n"},{"id":"394282","messageId":"20200329143329.13846-4-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200325143608.45141-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v2 3/4] transport-helper: mark failure for atomic push","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-29T14:33:28Z","receivedAt":"2020-03-29T14:33:43Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nCommit v2.22.0-1-g3bca1e7f9f (transport-helper: enforce atomic in\npush_refs_with_push, 2019-07-11) noticed the incomplete report of\nfailure of an atomic push for HTTP protocol.  But the implementation\nhas a flaw that mark all remote references as failure.\n\nOnly mark necessary references as failure in `push_refs_with_push()` of\ntransport-helper.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5541-http-push-smart.sh | 14 +++++++++++---\n transport-helper.c         | 15 +++++++++++++++\n 2 files changed, 26 insertions(+), 3 deletions(-)\n\ndiff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh\nindex 2c2c3fb0f5..afc680d5e3 100755\n--- a/t/t5541-http-push-smart.sh\n+++ b/t/t5541-http-push-smart.sh\n@@ -177,7 +177,10 @@ test_expect_success 'push (chunked)' '\n \t test $HEAD = $(git rev-parse --verify HEAD))\n '\n \n-test_expect_failure 'push --atomic also prevents branch creation, reports collateral' '\n+## References of remote: atomic1(1)            master(2) collateral(2) other(2)\n+## References of local :            atomic2(2) master(1) collateral(3) other(2) collateral1(3) atomic(1)\n+## Atomic push         :                       master(1) collateral(3)                         atomic(1)\n+test_expect_success 'push --atomic also prevents branch creation, reports collateral' '\n \t# Setup upstream repo - empty for now\n \td=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&\n \tgit init --bare \"$d\" &&\n@@ -189,7 +192,8 @@ test_expect_failure 'push --atomic also prevents branch creation, reports collat\n \ttest_commit atomic2 &&\n \tgit branch collateral &&\n \tgit branch other &&\n-\tgit push \"$up\" master collateral other &&\n+\tgit push \"$up\" atomic1 master collateral other &&\n+\tgit tag -d atomic1 &&\n \n \t# collateral is a valid push, but should be failed by atomic push\n \tgit checkout collateral &&\n@@ -224,7 +228,11 @@ test_expect_failure 'push --atomic also prevents branch creation, reports collat\n \n \t# the collateral failure refs should be indicated to the user\n \tgrep \"^ ! .*rejected.* atomic -> atomic .*atomic push failed\" output &&\n-\tgrep \"^ ! .*rejected.* collateral -> collateral .*atomic push failed\" output\n+\tgrep \"^ ! .*rejected.* collateral -> collateral .*atomic push failed\" output &&\n+\n+\t# never report what we do not push\n+\t! grep \"^ ! .*rejected.* atomic1 \" output &&\n+\t! grep \"^ ! .*rejected.* other \" output\n '\n \n test_expect_success 'push --atomic fails on server-side errors' '\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 20a7185ec4..ab3b52eb14 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -894,6 +894,21 @@ static int push_refs_with_push(struct transport *transport,\n \t\tcase REF_STATUS_REJECT_STALE:\n \t\tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n \t\t\tif (atomic) {\n+\t\t\t\t/* Mark other refs as failed */\n+\t\t\t\tfor (ref = remote_refs; ref; ref = ref->next) {\n+\t\t\t\t\tif (!ref->peer_ref && !mirror)\n+\t\t\t\t\t\tcontinue;\n+\n+\t\t\t\t\tswitch (ref->status) {\n+\t\t\t\t\tcase REF_STATUS_NONE:\n+\t\t\t\t\tcase REF_STATUS_OK:\n+\t\t\t\t\tcase REF_STATUS_EXPECTING_REPORT:\n+\t\t\t\t\t\tref->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\tdefault:\n+\t\t\t\t\t\tbreak; /* do nothing */\n+\t\t\t\t\t}\n+\t\t\t\t}\n \t\t\t\tstring_list_clear(&cas_options, 0);\n \t\t\t\treturn 0;\n \t\t\t} else\n-- \n2.26.0.4.g39bcdcb101.dirty\n\n"},{"id":"394283","messageId":"CANYiYbH1eaw6WT8qPfc3afwEDnv07uhq7_92VpdBgDKzZhFqcA@mail.gmail.com","threadId":"52936","inReplyTo":"20200329143329.13846-1-worldhello.net@gmail.com","subject":"Re: [PATCH v2 0/4] Never report references we not push","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-29T14:35:47Z","receivedAt":"2020-03-29T14:36:00Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> 于2020年3月29日周日 下午10:33写道：\n>\n> ## Changes since v1:\n>\n> * Add a new common method `reject_atomic_push()`, which is reused for\n>   atomic push rejection for SSH and HTTP protocol.\n\nI first try to add some common methods in `transport_push()` of\n\"transport.c\", but I find it is hard to do this.  Because the\ncapabilities has not been set after calling\n`transport->vtable->get_refs_list` for HTTP protocol.\n\nAnd for the same reason, the following code in `send-pack.c` cannot\nmove to `set_ref_status_for_push()`.\n\n    /*\n     * NEEDSWORK: why does delete-refs have to be so specific to\n     * send-pack machinery that set_ref_status_for_push() cannot\n     * set this bit for us???\n     */\n    for (ref = remote_refs; ref; ref = ref->next)\n        if (ref->deletion && !allow_deleting_refs)\n                ref->status = REF_STATUS_REJECT_NODELETE\n\nI wonder if we can extend method `get_refs_list()` of\n\"transport-helper.c\"  and \"remote-curl.c\", so we can all method\n`server_supports()` in `transport_push()` of \"transport.c\".\n"},{"id":"394373","messageId":"20200330165754.19195-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v5 1/6] transport: not report a non-head push as a branch","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-30T16:57:49Z","receivedAt":"2020-03-30T16:58:07Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing a new reference (not a head or tag), report it as a new\nreference instead of a new branch.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411-proc-receive-hook.sh | 153 +++++++++++++++++++++++++++++++++++\n t/t5516-fetch-push.sh        |   2 +-\n transport.c                  |   9 ++-\n 3 files changed, 160 insertions(+), 4 deletions(-)\n create mode 100755 t/t5411-proc-receive-hook.sh\n\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nnew file mode 100755\nindex 0000000000..1784bcb584\n--- /dev/null\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -0,0 +1,153 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook'\n+\n+. ./test-lib.sh\n+\n+# Create commits in <repo> and assign each commit's oid to shell variables\n+# given in the arguments (A, B, and C). E.g.:\n+#\n+#     create_commits_in <repo> A B C\n+#\n+# NOTE: Never calling this function from a subshell since variable\n+# assignments will disappear when subshell exits.\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{})\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  We can easily prepare the expect text\n+# without having to worry about future changes of the commit ID and spaces\n+# of the output.  We also replce single quotes with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect txt.\n+make_user_friendly_and_stable_output () {\n+\tsed \\\n+\t\t-e \"s/  *\\$//\" \\\n+\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/'/\\\"/g\" \\\n+\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n+\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n+\t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n+\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n+\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\"\n+}\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+test_expect_success \"setup\" '\n+\tgit init --bare upstream &&\n+\tgit init workbench &&\n+\tcreate_commits_in workbench A B &&\n+\t(\n+\t\tcd workbench &&\n+\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\tgit config core.abbrev 7 &&\n+\t\tgit remote add origin ../upstream &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit tag -m \"v123\" v123 $A &&\n+\t\tgit push origin \\\n+\t\t\t$B:refs/heads/master \\\n+\t\t\t$A:refs/heads/next\n+\t) &&\n+\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t# setup pre-receive hook\n+\tcat >upstream/hooks/pre-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# pre-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"pre-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\t# setup post-receive hook\n+\tcat >upstream/hooks/post-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# post-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"post-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\tchmod a+x \\\n+\t\tupstream/hooks/pre-receive \\\n+\t\tupstream/hooks/post-receive\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"normal git-push command\" '\n+\tgit -C workbench push -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tHEAD:refs/heads/master \\\n+\t\tHEAD:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo ../upstream\n+\t + <OID>...<OID> HEAD -> master (forced update)\n+\t - [deleted] next\n+\t * [new tag] v123 -> v123\n+\t * [new reference] HEAD -> refs/review/master/topic\n+\t * [new branch] HEAD -> a/b/c\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 9ff041a093..9e4b9313b5 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1039,7 +1039,7 @@ test_force_fetch_tag \"annotated tag\" \"-f -a -m'tag message'\"\n test_expect_success 'push --porcelain' '\n \tmk_empty testrepo &&\n \techo >.git/foo  \"To testrepo\" &&\n-\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new branch]\"  &&\n+\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new reference]\"  &&\n \techo >>.git/foo \"Done\" &&\n \tgit push >.git/bar --porcelain  testrepo refs/heads/master:refs/remotes/origin/master &&\n \t(\ndiff --git a/transport.c b/transport.c\nindex 1fdc7dac1a..272c0f4046 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -500,9 +500,12 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\t\t\t porcelain, summary_width);\n \telse if (is_null_oid(&ref->old_oid))\n \t\tprint_ref_status('*',\n-\t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n-\t\t\t\"[new branch]\"),\n-\t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t  ? \"[new tag]\"\n+\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t     ? \"[new branch]\"\n+\t\t\t\t     : \"[new reference]\")),\n+\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n-- \n2.26.0.4.g39bcdcb101.dirty\n\n"},{"id":"394375","messageId":"20200330165754.19195-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v5 0/6] New proc-receive hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-30T16:57:48Z","receivedAt":"2020-03-30T16:58:10Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\n## Changes since v4\n\n* Fixed code review issues.\n* Reimplement `read_proc_receive_result()` of patch 2/6.\n* Add new parameter for function `print_ref_status()` of patch 5/6.\n* Documentation in patch 6/6.\n\n\n## Range-diff v4...v5\n\n1:  7c08735833 ! 1:  a8bcc20439 transport: not report a non-head push as a branch\n    @@ t/t5411-proc-receive-hook.sh (new)\n     +#\n     +#     create_commits_in <repo> A B C\n     +#\n    -+# NOTE: Avoid calling this function from a subshell since variable\n    ++# NOTE: Never calling this function from a subshell since variable\n     +# assignments will disappear when subshell exits.\n     +create_commits_in () {\n     +\trepo=\"$1\" &&\n    -+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{} 2>/dev/null)\n    ++\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{})\n     +\tthen\n     +\t\tparent=\n     +\tfi &&\n    @@ t/t5411-proc-receive-hook.sh (new)\n     +\tgit -C \"$repo\" update-ref refs/heads/master $oid\n     +}\n     +\n    -+format_git_output () {\n    ++# Format the output of git-push, git-show-ref and other commands to make a\n    ++# user-friendly and stable text.  We can easily prepare the expect text\n    ++# without having to worry about future changes of the commit ID and spaces\n    ++# of the output.  We also replce single quotes with double quotes, because\n    ++# it is boring to prepare unquoted single quotes in expect txt.\n    ++make_user_friendly_and_stable_output () {\n     +\tsed \\\n    -+\t\t-e \"s/  *\\$//g\" \\\n    ++\t\t-e \"s/  *\\$//\" \\\n    ++\t\t-e \"s/   */ /g\" \\\n    ++\t\t-e \"s/'/\\\"/g\" \\\n     +\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n     +\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n    -+\t\t-e \"s/$TAG/<COMMIT-T>/g\" \\\n    ++\t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n     +\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n    -+\t\t-e \"s/'/\\\"/g\"\n    ++\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\"\n     +}\n     +\n    ++# Refs of upstream : master(B)  next(A)\n    ++# Refs of workbench: master(A)           tags/v123\n     +test_expect_success \"setup\" '\n     +\tgit init --bare upstream &&\n     +\tgit init workbench &&\n     +\tcreate_commits_in workbench A B &&\n     +\t(\n     +\t\tcd workbench &&\n    -+\t\tgit remote add origin ../upstream &&\n    ++\t\t# Try to make a stable fixed width for abbreviated commit ID,\n    ++\t\t# this fixed-width oid will be replaced with \"<OID>\".\n     +\t\tgit config core.abbrev 7 &&\n    ++\t\tgit remote add origin ../upstream &&\n     +\t\tgit update-ref refs/heads/master $A &&\n    -+\t\tgit tag -m \"v1.0.0\" v1.0.0 $A &&\n    ++\t\tgit tag -m \"v123\" v123 $A &&\n     +\t\tgit push origin \\\n     +\t\t\t$B:refs/heads/master \\\n     +\t\t\t$A:refs/heads/next\n     +\t) &&\n    -+\tTAG=$(cd workbench; git rev-parse v1.0.0) &&\n    ++\tTAG=$(git -C workbench rev-parse v123) &&\n     +\n     +\t# setup pre-receive hook\n    -+\tcat >upstream/hooks/pre-receive <<-EOF &&\n    ++\tcat >upstream/hooks/pre-receive <<-\\EOF &&\n     +\t#!/bin/sh\n     +\n    -+\tprintf >&2 \"# pre-receive hook\\n\"\n    ++\techo >&2 \"# pre-receive hook\"\n     +\n     +\twhile read old new ref\n     +\tdo\n    -+\t\tprintf >&2 \"pre-receive< \\$old \\$new \\$ref\\n\"\n    ++\t\techo >&2 \"pre-receive< $old $new $ref\"\n     +\tdone\n     +\tEOF\n     +\n     +\t# setup post-receive hook\n    -+\tcat >upstream/hooks/post-receive <<-EOF &&\n    ++\tcat >upstream/hooks/post-receive <<-\\EOF &&\n     +\t#!/bin/sh\n     +\n    -+\tprintf >&2 \"# post-receive hook\\n\"\n    ++\techo >&2 \"# post-receive hook\"\n     +\n     +\twhile read old new ref\n     +\tdo\n    -+\t\tprintf >&2 \"post-receive< \\$old \\$new \\$ref\\n\"\n    ++\t\techo >&2 \"post-receive< $old $new $ref\"\n     +\tdone\n     +\tEOF\n     +\n    @@ t/t5411-proc-receive-hook.sh (new)\n     +\t\tupstream/hooks/post-receive\n     +'\n     +\n    ++# Refs of upstream : master(B)  next(A)\n    ++# Refs of workbench: master(A)           tags/v123\n    ++# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n     +test_expect_success \"normal git-push command\" '\n    -+\t(\n    -+\t\tcd workbench &&\n    -+\t\tgit push -f origin \\\n    -+\t\t\trefs/tags/v1.0.0 \\\n    -+\t\t\t:refs/heads/next \\\n    -+\t\t\tHEAD:refs/heads/master \\\n    -+\t\t\tHEAD:refs/review/master/topic \\\n    -+\t\t\tHEAD:refs/heads/a/b/c\n    -+\t) >out 2>&1 &&\n    -+\tformat_git_output <out >actual &&\n    ++\tgit -C workbench push -f origin \\\n    ++\t\trefs/tags/v123 \\\n    ++\t\t:refs/heads/next \\\n    ++\t\tHEAD:refs/heads/master \\\n    ++\t\tHEAD:refs/review/master/topic \\\n    ++\t\tHEAD:refs/heads/a/b/c \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: # pre-receive hook\n     +\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n     +\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-T> refs/tags/v1.0.0\n    ++\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n     +\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-T> refs/tags/v1.0.0\n    ++\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n     +\tTo ../upstream\n    -+\t + ce858e6...1029397 HEAD -> master (forced update)\n    -+\t - [deleted]         next\n    -+\t * [new tag]         v1.0.0 -> v1.0.0\n    -+\t * [new reference]   HEAD -> refs/review/master/topic\n    -+\t * [new branch]      HEAD -> a/b/c\n    ++\t + <OID>...<OID> HEAD -> master (forced update)\n    ++\t - [deleted] next\n    ++\t * [new tag] v123 -> v123\n    ++\t * [new reference] HEAD -> refs/review/master/topic\n    ++\t * [new branch] HEAD -> a/b/c\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit show-ref\n    -+\t) >out &&\n    -+\tformat_git_output <out >actual &&\n    ++\tgit -C upstream show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/a/b/c\n     +\t<COMMIT-A> refs/heads/master\n     +\t<COMMIT-A> refs/review/master/topic\n    -+\t<COMMIT-T> refs/tags/v1.0.0\n    ++\t<TAG-v123> refs/tags/v123\n     +\tEOF\n     +\ttest_cmp expect actual\n     +'\n     +\n     +test_done\n     \n    + ## t/t5516-fetch-push.sh ##\n    +@@ t/t5516-fetch-push.sh: test_force_fetch_tag \"annotated tag\" \"-f -a -m'tag message'\"\n    + test_expect_success 'push --porcelain' '\n    + \tmk_empty testrepo &&\n    + \techo >.git/foo  \"To testrepo\" &&\n    +-\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new branch]\"  &&\n    ++\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new reference]\"  &&\n    + \techo >>.git/foo \"Done\" &&\n    + \tgit push >.git/bar --porcelain  testrepo refs/heads/master:refs/remotes/origin/master &&\n    + \t(\n    +\n      ## transport.c ##\n     @@ transport.c: static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n    + \t\t\t\t porcelain, summary_width);\n      \telse if (is_null_oid(&ref->old_oid))\n      \t\tprint_ref_status('*',\n    - \t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n    +-\t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n     -\t\t\t\"[new branch]\"),\n    -+\t\t\t(starts_with(ref->name, \"refs/heads/\") ? \"[new branch]\" :\n    -+\t\t\t\"[new reference]\")),\n    - \t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n    +-\t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n    ++\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n    ++\t\t\t\t  ? \"[new tag]\"\n    ++\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n    ++\t\t\t\t     ? \"[new branch]\"\n    ++\t\t\t\t     : \"[new reference]\")),\n    ++\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n      \telse {\n      \t\tstruct strbuf quickref = STRBUF_INIT;\n    + \t\tchar type;\n2:  67dea721b7 ! 2:  14641ec57e receive-pack: add new proc-receive hook\n    @@ Commit message\n         We can use this \"proc-receive\" command to create pull requests or send\n         emails for code review.\n     \n    -    This \"proc-receive\" hook reads commands, push-options (optional), and\n    -    send result using a protocol in pkt-line format.  In the following\n    -    example, The letter \"S\" stands for \"receive-pack\" and letter \"H\" stands\n    -    for the hook.\n    +    Suggested by Junio, this \"proc-receive\" hook reads the commands,\n    +    push-options (optional), and send result using a protocol in pkt-line\n    +    format.  In the following example, The letter \"S\" stands for\n    +    \"receive-pack\" and letter \"H\" stands for the hook.\n     \n    -        S: PKT-LINE(version=1\\0push-options ...)\n    +        # Version and capabilities negotiation.\n    +        S: PKT-LINE(version=1\\0push-options atomic...)\n             S: flush-pkt\n    -\n    -        H: PKT-LINE(version=1\\0push-options ...)\n    +        H: PKT-LINE(version=1\\0push-options...)\n             H: flush-pkt\n     \n    +        # Send commands from server to the hook.\n             S: PKT-LINE(old-oid new-oid ref)\n             S: ... ...\n             S: flush-pkt\n    -\n    -        # Optional, only if push-options is negotiated.\n    +        # Only if push-options have been negotiated.\n             S: PKT-LINE(push-option)\n             S: ... ...\n             S: flush-pkt\n     \n    +        # Receive result from the hook.\n             # OK, run this command successfully.\n             H: PKT-LINE(old-oid new-oid ref ok)\n    -\n             # NO, I reject it.\n             H: PKT-LINE(old-oid new-oid ref ng reason)\n    -\n             # OK, but use an alternate reference. (in latter commit)\n             H: PKT-LINE(old-oid new-oid ref ok ref:alt-ref)\n    -\n             # It will fallthrough to receive-pack to execute. (in latter commit)\n             H: PKT-LINE(old-oid new-oid ref ft)\n    -\n             H: ... ...\n             H: flush-pkt\n     \n    @@ Commit message\n         the result to replace the commands that have specific `run_proc_receive`\n         field turned on.\n     \n    +    Suggested-by: Junio C Hamano <gitster@pobox.com>\n         Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n     \n      ## Makefile ##\n    @@ Makefile: TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n      TEST_BUILTINS_OBJS += test-read-cache.o\n     \n      ## builtin/receive-pack.c ##\n    -@@ builtin/receive-pack.c: struct command {\n    +@@ builtin/receive-pack.c: static void write_head_info(void)\n    + \tpacket_flush(1);\n    + }\n    + \n    ++#define RUN_PROC_RECEIVE_SCHEDULE\t1\n    ++#define RUN_PROC_RECEIVE_RETURNED\t2\n    + struct command {\n      \tstruct command *next;\n      \tconst char *error_string;\n      \tunsigned int skip_update:1,\n     -\t\t     did_not_exist:1;\n     +\t\t     did_not_exist:1,\n    -+\t\t     run_proc_receive:1;\n    ++\t\t     run_proc_receive:2;\n      \tint index;\n      \tstruct object_id old_oid;\n      \tstruct object_id new_oid;\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n      \treturn finish_command(&proc);\n      }\n      \n    ++static struct command *find_command_by_refname(const struct command *list,\n    ++\t\t\t\t\t       const char *refname)\n    ++{\n    ++\tfor ( ; list; list = list->next)\n    ++\t\tif (!strcmp(list->ref_name, refname))\n    ++\t\t\treturn (struct command *)list;\n    ++\treturn NULL;\n    ++}\n    ++\n     +static int read_proc_receive_result(struct packet_reader *reader,\n    -+\t\t\t\t    struct command **commands)\n    ++\t\t\t\t    struct command *commands)\n     +{\n    -+\tstruct command **tail = commands;\n    ++\tstruct command *hint;\n    ++\tstruct command *cmd;\n     +\tint code = 0;\n     +\n    ++\thint = NULL;\n     +\tfor (;;) {\n     +\t\tstruct object_id old_oid, new_oid;\n    -+\t\tstruct command *cmd;\n     +\t\tconst char *refname;\n     +\t\tconst char *p;\n     +\t\tchar *status;\n     +\t\tchar *msg = NULL;\n     +\n    -+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n    ++\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n     +\t\t\tbreak;\n    -+\t\t}\n    -+\n     +\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n     +\t\t    *p++ != ' ' ||\n     +\t\t    parse_oid_hex(p, &new_oid, &p) ||\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\t\tdie(\"protocol error: proc-receive has bad status '%s' for '%s'\",\n     +\t\t\t    status, reader->line);\n     +\n    -+\t\tFLEX_ALLOC_MEM(cmd, ref_name, refname, strlen(refname));\n    -+\t\toidcpy(&cmd->old_oid, &old_oid);\n    -+\t\toidcpy(&cmd->new_oid, &new_oid);\n    -+\t\tcmd->run_proc_receive = 1;\n    -+\n    ++\t\t/* first try searching at our hint, falling back to all refs */\n    ++\t\tif (hint)\n    ++\t\t\thint = find_command_by_refname(hint, refname);\n    ++\t\tif (!hint)\n    ++\t\t\thint = find_command_by_refname(commands, refname);\n    ++\t\tif (!hint) {\n    ++\t\t\twarning(\"proc-receive reported status on unknown ref: %s\",\n    ++\t\t\t\trefname);\n    ++\t\t\tcontinue;\n    ++\t\t}\n    ++\t\tif (!hint->run_proc_receive) {\n    ++\t\t\twarning(\"proc-receive reported status on ref of builtin command: %s\",\n    ++\t\t\t\trefname);\n    ++\t\t\tcontinue;\n    ++\t\t}\n    ++\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n    ++\t\toidcpy(&hint->old_oid, &old_oid);\n    ++\t\toidcpy(&hint->new_oid, &new_oid);\n     +\t\tif (!strcmp(status, \"ng\")) {\n     +\t\t\tif (msg)\n    -+\t\t\t\tcmd->error_string = xstrdup(msg);\n    ++\t\t\t\thint->error_string = xstrdup(msg);\n     +\t\t\telse\n    -+\t\t\t\tcmd->error_string = \"failed\";\n    ++\t\t\t\thint->error_string = \"failed\";\n     +\t\t\tcode = 1;\n     +\t\t} else if (strcmp(\"ok\", status)) {\n     +\t\t\tdie(\"protocol error: proc-receive has bad status '%s' for '%s'\",\n     +\t\t\t    status, reader->line);\n     +\t\t}\n    -+\n    -+\t\t*tail = cmd;\n    -+\t\ttail = &cmd->next;\n     +\t}\n    ++\n    ++\tfor (cmd = commands; cmd; cmd = cmd->next)\n    ++\t\tif (cmd->run_proc_receive &&\n    ++\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED))\n    ++\t\t    cmd->error_string = \"no report from proc-receive\";\n    ++\n     +\treturn code;\n     +}\n     +\n    -+static int run_proc_receive_hook(struct command **commands,\n    ++static int run_proc_receive_hook(struct command *commands,\n     +\t\t\t\t const struct string_list *push_options)\n     +{\n     +\tstruct child_process proc = CHILD_PROCESS_INIT;\n     +\tstruct async muxer;\n    -+\tstruct command *result_commands = NULL;\n     +\tstruct command *cmd;\n     +\tconst char *argv[2];\n     +\tstruct packet_reader reader;\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\tpacket_reader_init(&reader, proc.out, NULL, 0,\n     +\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n     +\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n    -+\tif (use_push_options)\n    -+\t\tstrbuf_addstr(&cap, \" push-options\");\n     +\tif (use_atomic)\n     +\t\tstrbuf_addstr(&cap, \" atomic\");\n    ++\tif (use_push_options)\n    ++\t\tstrbuf_addstr(&cap, \" push-options\");\n     +\tif (cap.len) {\n     +\t\tpacket_write_fmt(proc.in, \"version=1%c%s\\n\", '\\0', cap.buf + 1);\n     +\t\tstrbuf_release(&cap);\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\tdie(\"protocol error: unknown proc-receive version '%d'\", version);\n     +\n     +\t/* Send commands */\n    -+\tfor (cmd = *commands; cmd; cmd = cmd->next) {\n    ++\tfor (cmd = commands; cmd; cmd = cmd->next) {\n     +\t\tchar *old_hex, *new_hex;\n     +\n     +\t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t}\n     +\n     +\t/* Read result from proc-receive */\n    -+\tcode = read_proc_receive_result(&reader, &result_commands);\n    ++\tcode = read_proc_receive_result(&reader, commands);\n     +\tclose(proc.in);\n     +\tclose(proc.out);\n     +\tif (use_sideband)\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\n     +\tsigchain_pop(SIGPIPE);\n     +\n    -+\t/* After receiving the result from the \"proc-receive\" hook,\n    -+\t * \"receive-pack\" will use the result to replace commands that\n    -+\t * have specific `run_proc_receive` field.\n    -+\t */\n    -+\tfor (cmd = *commands; cmd; cmd = cmd->next)\n    -+\t\tif (!cmd->run_proc_receive)\n    -+\t\t\tbreak;\n    -+\n    -+\t/* Merge commands with result_commands and sort */\n    -+\tif (!cmd) {\n    -+\t\t*commands = result_commands;\n    -+\t} else {\n    -+\t\tstruct command *next_cmd = cmd;\n    -+\t\tstruct command *next_result = result_commands;\n    -+\t\tstruct command *head = NULL;\n    -+\t\tstruct command *tail = NULL;\n    -+\n    -+\t\tif (!next_result ||\n    -+\t\t    strcmp(next_cmd->ref_name, next_result->ref_name) < 0) {\n    -+\t\t\thead = next_cmd;\n    -+\t\t\tnext_cmd = next_cmd->next;\n    -+\t\t} else {\n    -+\t\t\thead = next_result;\n    -+\t\t\tnext_result = next_result->next;\n    -+\t\t}\n    -+\t\ttail = head;\n    -+\n    -+\t\tfor (;;) {\n    -+\t\t\tif (!next_cmd) {\n    -+\t\t\t\ttail->next = next_result;\n    -+\t\t\t\tbreak;\n    -+\t\t\t} else if (next_cmd->run_proc_receive) {\n    -+\t\t\t\tnext_cmd = next_cmd->next;\n    -+\t\t\t} else if (!next_result) {\n    -+\t\t\t\ttail->next = next_cmd;\n    -+\t\t\t\tnext_cmd = next_cmd->next;\n    -+\t\t\t\ttail = tail->next;\n    -+\t\t\t} else {\n    -+\t\t\t\tif (strcmp(next_cmd->ref_name, next_result->ref_name) < 0) {\n    -+\t\t\t\t\ttail->next = next_cmd;\n    -+\t\t\t\t\tnext_cmd = next_cmd->next;\n    -+\t\t\t\t\ttail = tail->next;\n    -+\t\t\t\t} else {\n    -+\t\t\t\t\ttail->next = next_result;\n    -+\t\t\t\t\tnext_result = next_result->next;\n    -+\t\t\t\t\ttail = tail->next;\n    -+\t\t\t\t}\n    -+\t\t\t}\n    -+\t\t}\n    -+\t\t*commands = head;\n    -+\t}\n    -+\n     +\treturn code;\n     +}\n     +\n    @@ builtin/receive-pack.c: static void execute_commands_atomic(struct command *comm\n      \t\t\tcontinue;\n      \n      \t\tcmd->error_string = update(cmd, si);\n    -@@ builtin/receive-pack.c: static void execute_commands_atomic(struct command *commands,\n    - \tstrbuf_release(&err);\n    - }\n    - \n    --static void execute_commands(struct command *commands,\n    -+static void execute_commands(struct command **orig_commands,\n    - \t\t\t     const char *unpacker_error,\n    - \t\t\t     struct shallow_info *si,\n    - \t\t\t     const struct string_list *push_options)\n    - {\n    -+\tstruct command *commands = *orig_commands;\n    - \tstruct check_connected_options opt = CHECK_CONNECTED_INIT;\n    - \tstruct command *cmd;\n    +@@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n      \tstruct iterate_data data;\n      \tstruct async muxer;\n      \tint err_fd = 0;\n    @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n     +\tif (run_proc_receive) {\n     +\t\tint code;\n     +\n    -+\t\tcode = run_proc_receive_hook(orig_commands, push_options);\n    -+\t\tcommands = *orig_commands;\n    ++\t\tcode = run_proc_receive_hook(commands, push_options);\n     +\t\tif (code) {\n     +\t\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n     +\t\t\t\tif (!cmd->error_string  && (cmd->run_proc_receive || use_atomic))\n    @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n      \tif (use_atomic)\n      \t\texecute_commands_atomic(commands, si);\n      \telse\n    -@@ builtin/receive-pack.c: int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n    - \t\t\tupdate_shallow_info(commands, &si, &ref);\n    - \t\t}\n    - \t\tuse_keepalive = KEEPALIVE_ALWAYS;\n    --\t\texecute_commands(commands, unpack_status, &si,\n    -+\t\texecute_commands(&commands, unpack_status, &si,\n    - \t\t\t\t &push_options);\n    - \t\tif (pack_lockfile)\n    - \t\t\tunlink_or_warn(pack_lockfile);\n     \n      ## t/helper/test-proc-receive.c (new) ##\n     @@\n    @@ t/helper/test-proc-receive.c (new)\n     +static int version = 1;\n     +static int verbose = 0;\n     +static int no_push_options = 0;\n    ++static int use_atomic = 0;\n     +static int use_push_options = 0;\n     +static struct string_list returns = STRING_LIST_INIT_NODUP;\n     +\n    @@ t/helper/test-proc-receive.c (new)\n     +\t\t\tlinelen = strlen(reader->line);\n     +\t\t\tif (linelen < reader->pktlen) {\n     +\t\t\t\tconst char *feature_list = reader->line + linelen + 1;\n    ++\t\t\t\tif (parse_feature_request(feature_list, \"atomic\"))\n    ++\t\t\t\t\tuse_atomic= 1;\n     +\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n     +\t\t\t\t\tuse_push_options = 1;\n     +\t\t\t}\n    @@ t/helper/test-proc-receive.c (new)\n     +\tif (verbose) {\n     +\t\tstruct command *cmd;\n     +\n    ++\t\tif (use_push_options || use_atomic)\n    ++\t\t\tfprintf(stderr, \"proc-receive:%s%s\\n\",\n    ++\t\t\t\tuse_atomic? \" atomic\": \"\",\n    ++\t\t\t\tuse_push_options ? \" push_options\": \"\");\n    ++\n     +\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n     +\t\t\tchar *old_hex, *new_hex;\n     +\n    @@ t/helper/test-tool.h: int cmd__parse_pathspec_file(int argc, const char** argv);\n      int cmd__read_cache(int argc, const char **argv);\n     \n      ## t/t5411-proc-receive-hook.sh ##\n    -@@ t/t5411-proc-receive-hook.sh: format_git_output () {\n    - \t\t-e \"s/'/\\\"/g\"\n    +@@ t/t5411-proc-receive-hook.sh: make_user_friendly_and_stable_output () {\n    + \t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\"\n      }\n      \n     +# Asynchronous sideband may generate inconsistent output messages,\n     +# sort before comparison.\n     +test_sorted_cmp () {\n    -+\tif ! $GIT_TEST_CMP \"$@\"\n    ++\tif ! $GIT_TEST_CMP \"$@\" >/dev/null 2>&1\n     +\tthen\n     +\t\tcmd=$GIT_TEST_CMP\n     +\t\tfor f in \"$@\"\n    @@ t/t5411-proc-receive-hook.sh: format_git_output () {\n     +\t\t\tsort \"$f\" >\"$f.sorted\"\n     +\t\t\tcmd=\"$cmd \\\"$f.sorted\\\"\"\n     +\t\tdone\n    -+\t\tif ! eval $cmd\n    ++\t\tif ! eval $cmd >/dev/null 2>&1\n     +\t\tthen\n     +\t\t\t$GIT_TEST_CMP \"$@\"\n     +\t\tfi\n     +\tfi\n     +}\n     +\n    + # Refs of upstream : master(B)  next(A)\n    + # Refs of workbench: master(A)           tags/v123\n      test_expect_success \"setup\" '\n    - \tgit init --bare upstream &&\n    - \tgit init workbench &&\n     @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n      \ttest_cmp expect actual\n      '\n      \n    ++# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n    ++# Refs of workbench: master(A)  tags/v123\n     +test_expect_success \"cleanup\" '\n     +\t(\n     +\t\tcd upstream &&\n     +\t\tgit update-ref -d refs/review/master/topic &&\n    -+\t\tgit update-ref -d refs/tags/v1.0.0 &&\n    ++\t\tgit update-ref -d refs/tags/v123 &&\n     +\t\tgit update-ref -d refs/heads/a/b/c\n     +\t)\n     +'\n     +\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       next(A)  refs/for/master/topic(A)\n     +test_expect_success \"no proc-receive hook, fail to push special ref\" '\n    -+\t(\n    -+\t\tcd workbench &&\n    -+\t\ttest_must_fail git push origin \\\n    -+\t\t\tHEAD:next \\\n    -+\t\t\tHEAD:refs/for/master/topic\n    -+\t) >out 2>&1 &&\n    -+\tformat_git_output <out >actual &&\n    ++\ttest_must_fail git -C workbench push origin \\\n    ++\t\tHEAD:next \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: # pre-receive hook\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n     +\tTo ../upstream\n    -+\t * [new branch]      HEAD -> next\n    ++\t * [new branch] HEAD -> next\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\terror: failed to push some refs to \"../upstream\"\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit show-ref\n    -+\t) >out &&\n    -+\tformat_git_output <out >actual &&\n    ++\tgit -C upstream show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n     +\t<COMMIT-A> refs/heads/next\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\ttest_cmp expect actual\n     +'\n     +\n    ++# Refs of upstream : master(A)             next(A)\n    ++# Refs of workbench: master(A)  tags/v123\n     +test_expect_success \"cleanup\" '\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit update-ref -d refs/heads/next\n    -+\t)\n    ++\tgit -C upstream update-ref -d refs/heads/next\n     +'\n     +\n    -+# TODO: report for the failure of master branch is unnecessary.\n    -+test_expect_success \"no proc-receive hook, fail all for atomic push\" '\n    -+\t(\n    -+\t\tcd workbench &&\n    -+\t\ttest_must_fail git push --atomic origin \\\n    -+\t\t\tHEAD:next \\\n    -+\t\t\tHEAD:refs/for/master/topic\n    -+\t) >out 2>&1 &&\n    -+\tformat_git_output <out >actual &&\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push --atomic:                       next(A)  refs/for/master/topic(A)\n    ++test_expect_failure \"no proc-receive hook, fail all for atomic push\" '\n    ++\ttest_must_fail git -C workbench push --atomic origin \\\n    ++\t\tHEAD:next \\\n    ++\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: # pre-receive hook\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: error: cannot to find hook \"proc-receive\"\n     +\tTo ../upstream\n    -+\t ! [rejected]        master (atomic push failed)\n     +\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\terror: failed to push some refs to \"../upstream\"\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit show-ref\n    -+\t) >out &&\n    -+\tformat_git_output <out >actual &&\n    ++\tgit -C upstream show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n     +\tEOF\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tchmod a+x upstream/hooks/proc-receive\n     +'\n     +\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/master/topic(A)\n     +test_expect_success \"proc-receive bad protocol: unknown version\" '\n    -+\t(\n    -+\t\tcd workbench &&\n    -+\t\ttest_must_fail git push origin \\\n    -+\t\t\tHEAD:refs/for/master/topic\n    -+\t) >out 2>&1 &&\n    -+\tformat_git_output <out | grep \"protocol error\" >actual &&\n    ++\ttest_must_fail git -C workbench push origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out | grep \"protocol error\" >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tfatal: protocol error: unknown proc-receive version \"2\"\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit show-ref\n    -+\t) >out &&\n    -+\tformat_git_output <out >actual &&\n    ++\tgit -C upstream show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n     +\tEOF\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tEOF\n     +'\n     +\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       next(A)  refs/for/master/topic(A)\n     +test_expect_success \"proc-receive bad protocol: no report\" '\n    -+\t(\n    -+\t\tcd workbench &&\n    -+\t\ttest_must_fail git push origin \\\n    -+\t\t\tHEAD:refs/for/master/topic\n    -+\t) >out 2>&1 &&\n    -+\tformat_git_output <out >actual &&\n    ++\ttest_must_fail git -C workbench push origin \\\n    ++\t\tHEAD:refs/heads/next \\\n    ++\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n     +\tTo ../upstream\n    -+\t ! [remote failure]  HEAD -> refs/for/master/topic (remote failed to report status)\n    ++\t * [new branch] HEAD -> next\n    ++\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n     +\terror: failed to push some refs to \"../upstream\"\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit show-ref\n    -+\t) >out &&\n    -+\tformat_git_output <out >actual &&\n    ++\tgit -C upstream show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n    ++\t<COMMIT-A> refs/heads/next\n     +\tEOF\n     +\ttest_cmp expect actual\n     +'\n     +\n    ++# Refs of upstream : master(A)             next(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++test_expect_success \"cleanup\" '\n    ++\tgit -C upstream update-ref -d refs/heads/next\n    ++\n    ++'\n    ++\n     +test_expect_success \"setup proc-receive hook (bad oid)\" '\n     +\tcat >upstream/hooks/proc-receive <<-EOF\n     +\t#!/bin/sh\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tEOF\n     +'\n     +\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/master/topic\n     +test_expect_success \"proc-receive bad protocol: bad oid\" '\n    -+\t(\n    -+\t\tcd workbench &&\n    -+\t\ttest_must_fail git push origin \\\n    -+\t\t\tHEAD:refs/for/master/topic\n    -+\t) >out 2>&1 &&\n    -+\tformat_git_output <out | grep \"protocol error\" >actual &&\n    ++\ttest_must_fail git -C workbench push origin \\\n    ++\t\tHEAD:refs/for/master/topic\\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out | grep \"protocol error\" >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tfatal: protocol error: proc-receive expected \"old new ref status [msg]\", got \"bad-id new-id ref ok\"\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit show-ref\n    -+\t) >out &&\n    -+\tformat_git_output <out >actual &&\n    ++\tgit -C upstream show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n     +\tEOF\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tEOF\n     +'\n     +\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/master/topic\n     +test_expect_success \"proc-receive bad protocol: no status\" '\n    -+\t(\n    -+\t\tcd workbench &&\n    -+\t\ttest_must_fail git push origin \\\n    -+\t\t\tHEAD:refs/for/master/topic\n    -+\t) >out 2>&1 &&\n    -+\tformat_git_output <out | grep \"protocol error\" >actual &&\n    ++\ttest_must_fail git -C workbench push origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out | grep \"protocol error\" >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tfatal: protocol error: proc-receive expected \"old new ref status [msg]\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit show-ref\n    -+\t) >out &&\n    -+\tformat_git_output <out >actual &&\n    ++\tgit -C upstream show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n     +\tEOF\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tEOF\n     +'\n     +\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/master/topic\n     +test_expect_success \"proc-receive bad protocol: unknown status\" '\n    -+\t(\n    -+\t\tcd workbench &&\n    -+\t\ttest_must_fail git push origin \\\n    -+\t\t\tHEAD:refs/for/master/topic\n    -+\t) >out 2>&1 &&\n    -+\tformat_git_output <out | grep \"protocol error\" >actual &&\n    ++\ttest_must_fail git -C workbench push origin \\\n    ++\t\t\tHEAD:refs/for/master/topic \\\n    ++\t\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out | grep \"protocol error\" >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tfatal: protocol error: proc-receive has bad status \"xx\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit show-ref\n    -+\t) >out &&\n    -+\tformat_git_output <out >actual &&\n    ++\tgit -C upstream show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n     +\tEOF\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tEOF\n     +'\n     +\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/master/topic\n     +test_expect_success \"proc-receive bad protocol: bad status\" '\n    -+\t(\n    -+\t\tcd workbench &&\n    -+\t\ttest_must_fail git push origin \\\n    -+\t\t\tHEAD:refs/for/master/topic\n    -+\t) >out 2>&1 &&\n    -+\tformat_git_output <out | grep \"protocol error\" >actual &&\n    ++\ttest_must_fail git -C workbench push origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out | grep \"protocol error\" >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tfatal: protocol error: proc-receive has bad status \"bad status\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit show-ref\n    -+\t) >out &&\n    -+\tformat_git_output <out >actual &&\n    ++\tgit -C upstream show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n     +\tEOF\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tEOF\n     +'\n     +\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/master/topic\n     +test_expect_success \"proc-receive: fail to update (no message)\" '\n    -+\t(\n    -+\t\tcd workbench &&\n    -+\t\ttest_must_fail git push origin \\\n    -+\t\t\tHEAD:refs/for/master/topic\n    -+\t) >out 2>&1 &&\n    -+\tformat_git_output <out >actual &&\n    ++\ttest_must_fail git -C workbench push origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: # pre-receive hook\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\terror: failed to push some refs to \"../upstream\"\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit show-ref\n    -+\t) >out &&\n    -+\tformat_git_output <out >actual &&\n    ++\tgit -C upstream show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n     +\tEOF\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tEOF\n     +'\n     +\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/master/topic\n     +test_expect_success \"proc-receive: fail to update (has message)\" '\n    -+\t(\n    -+\t\tcd workbench &&\n    -+\t\ttest_must_fail git push origin \\\n    -+\t\t\tHEAD:refs/for/master/topic\n    -+\t) >out 2>&1 &&\n    -+\tformat_git_output <out >actual &&\n    ++\ttest_must_fail git -C workbench push origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: # pre-receive hook\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\terror: failed to push some refs to \"../upstream\"\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit show-ref\n    -+\t) >out &&\n    -+\tformat_git_output <out >actual &&\n    ++\tgit -C upstream show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n     +\tEOF\n     +\ttest_cmp expect actual\n     +'\n     +\n    ++test_expect_success \"setup proc-receive hook (report status on builtin command)\" '\n    ++\tcat >upstream/hooks/proc-receive <<-EOF\n    ++\t#!/bin/sh\n    ++\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"$ZERO_OID $A refs/heads/master ok\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         : (B)                   refs/for/master/topic\n    ++test_expect_success \"proc-receive: warning on report for builtin command\" '\n    ++\ttest_must_fail git -C workbench push origin \\\n    ++\t\t$B:refs/heads/master \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/heads/master ok\n    ++\twarning: proc-receive reported status on ref of builtin command: refs/heads/master\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n    ++\tTo ../upstream\n    ++\t <OID>..<OID> <COMMIT-B> -> master\n    ++\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n    ++\terror: failed to push some refs to \"../upstream\"\n    ++\tEOF\n    ++\ttest_sorted_cmp expect actual &&\n    ++\tgit -C upstream show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-B> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++test_expect_success \"cleanup\" '\n    ++\tgit -C upstream update-ref refs/heads/master $A\n    ++'\n    ++\n     +test_expect_success \"setup proc-receive hook (ok)\" '\n     +\tcat >upstream/hooks/proc-receive <<-EOF\n     +\t#!/bin/sh\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tEOF\n     +'\n     +\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/master/topic\n     +test_expect_success \"proc-receive: ok\" '\n    -+\t(\n    -+\t\tcd workbench &&\n    -+\t\tgit push origin \\\n    -+\t\t\tHEAD:refs/for/master/topic\n    -+\t) >out 2>&1 &&\n    -+\tformat_git_output <out >actual &&\n    ++\tgit -C workbench push origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: # pre-receive hook\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo ../upstream\n    -+\t * [new reference]   HEAD -> refs/for/master/topic\n    ++\t * [new reference] HEAD -> refs/for/master/topic\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit show-ref\n    -+\t) >out &&\n    -+\tformat_git_output <out >actual &&\n    ++\tgit -C upstream show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n     +\tEOF\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"proc-receive: report unknown ref\" '\n    -+\t(\n    -+\t\tcd workbench &&\n    -+\t\ttest_must_fail git push origin \\\n    -+\t\t\tHEAD:refs/for/a/b/c/my/topic\n    -+\t) >out 2>&1 &&\n    -+\tformat_git_output <out >actual &&\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/a/b/c/my/topic\n    ++test_expect_success \"proc-receive: no report from proc-receive\" '\n    ++\ttest_must_fail git -C workbench push origin \\\n    ++\t\tHEAD:refs/for/a/b/c/my/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: # pre-receive hook\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n    -+\twarning: remote reported status on unknown ref: refs/for/master/topic\n    -+\tremote: # post-receive hook\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\twarning: proc-receive reported status on unknown ref: refs/for/master/topic\n     +\tTo ../upstream\n    -+\t ! [remote failure]  HEAD -> refs/for/a/b/c/my/topic (remote failed to report status)\n    ++\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (no report from proc-receive)\n     +\terror: failed to push some refs to \"../upstream\"\n     +\tEOF\n    -+\ttest_cmp expect actual &&\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit show-ref\n    -+\t) >out &&\n    -+\tformat_git_output <out >actual &&\n    ++\ttest_sorted_cmp expect actual &&\n    ++\tgit -C upstream show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n     +\tEOF\n     +\ttest_cmp expect actual\n     +'\n     +\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push -o ...  :                       refs/for/master/topic\n     +test_expect_success \"not support push options\" '\n    -+\t(\n    -+\t\tcd workbench &&\n    -+\t\ttest_must_fail git push \\\n    -+\t\t\t-o issue=123 \\\n    -+\t\t\t-o reviewer=user1 \\\n    -+\t\t\torigin \\\n    -+\t\t\tHEAD:refs/for/master/topic\n    -+\t) >out 2>&1 &&\n    -+\tformat_git_output <out >actual &&\n    ++\ttest_must_fail git -C workbench push \\\n    ++\t\t-o issue=123 \\\n    ++\t\t-o reviewer=user1 \\\n    ++\t\torigin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tfatal: the receiving end does not support push options\n     +\tfatal: the remote end hung up unexpectedly\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit show-ref\n    -+\t) >out &&\n    -+\tformat_git_output <out >actual &&\n    ++\tgit -C upstream show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n     +\tEOF\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +'\n     +\n     +test_expect_success \"enable push options\" '\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit config receive.advertisePushOptions true\n    -+\t)\n    ++\tgit -C upstream config receive.advertisePushOptions true\n     +'\n     +\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push -o ...  :                       next(A)  refs/for/master/topic\n     +test_expect_success \"push with options\" '\n    -+\t(\n    -+\t\tcd workbench &&\n    -+\t\tgit push \\\n    -+\t\t\t-o issue=123 \\\n    -+\t\t\t-o reviewer=user1 \\\n    -+\t\t\torigin \\\n    -+\t\t\tHEAD:refs/heads/next \\\n    -+\t\t\tHEAD:refs/for/master/topic\n    -+\t) >out 2>&1 &&\n    -+\tformat_git_output <out >actual &&\n    ++\tgit -C workbench push \\\n    ++\t\t--atomic \\\n    ++\t\t-o issue=123 \\\n    ++\t\t-o reviewer=user1 \\\n    ++\t\torigin \\\n    ++\t\tHEAD:refs/heads/next \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: # pre-receive hook\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n    ++\tremote: proc-receive: atomic push_options\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive< issue=123\n     +\tremote: proc-receive< reviewer=user1\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n     +\tremote: # post-receive hook\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo ../upstream\n    -+\t * [new branch]      HEAD -> next\n    -+\t * [new reference]   HEAD -> refs/for/master/topic\n    ++\t * [new branch] HEAD -> next\n    ++\t * [new reference] HEAD -> refs/for/master/topic\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit show-ref\n    -+\t) >out &&\n    -+\tformat_git_output <out >actual &&\n    ++\tgit -C upstream show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n     +\t<COMMIT-A> refs/heads/next\n3:  d3d4ee428b = 3:  2440a474bd refs.c: refactor to reuse ref_is_hidden()\n4:  5e049d89b1 ! 4:  e041582643 receive-pack: new config receive.procReceiveRefs\n    @@ builtin/receive-pack.c: static int receive_pack_config(const char *var, const ch\n      \treturn git_default_config(var, value, cb);\n      }\n      \n    -@@ builtin/receive-pack.c: static void execute_commands(struct command **orig_commands,\n    +@@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n      \t/* Try to find commands that have special prefix in their reference names,\n      \t * and mark them to run an external \"proc-receive\" hook later.\n      \t */\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"cleanup\" '\n     +\t)\n     +'\n     +\n    - test_expect_success \"no proc-receive hook, fail to push special ref\" '\n    - \t(\n    - \t\tcd workbench &&\n    + # Refs of upstream : master(A)\n    + # Refs of workbench: master(A)  tags/v123\n    + # git push         :                       next(A)  refs/for/master/topic(A)\n     @@ t/t5411-proc-receive-hook.sh: test_expect_success \"push with options\" '\n      \ttest_cmp expect actual\n      '\n      \n    ++# Refs of upstream : master(A)             next(A)\n    ++# Refs of workbench: master(A)  tags/v123\n     +test_expect_success \"cleanup\" '\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit update-ref -d refs/heads/next\n    -+\t)\n    ++\tgit -C upstream update-ref -d refs/heads/next\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook\" '\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"push with options\" '\n     +\tchmod a+x upstream/hooks/proc-receive\n     +'\n     +\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/next/topic(A)  refs/review/a/b/c/topic(A)  refs/for/master/topic(A)\n     +test_expect_success \"report update of all special refs\" '\n    -+\t(\n    -+\t\tcd workbench &&\n    -+\t\tgit push origin \\\n    -+\t\t\tHEAD:refs/for/next/topic \\\n    -+\t\t\tHEAD:refs/review/a/b/c/topic \\\n    -+\t\t\tHEAD:refs/for/master/topic\n    -+\t) >out 2>&1 &&\n    -+\tformat_git_output <out >actual &&\n    ++\tgit -C workbench push origin \\\n    ++\t\tHEAD:refs/for/next/topic \\\n    ++\t\tHEAD:refs/review/a/b/c/topic \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: # pre-receive hook\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"push with options\" '\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo ../upstream\n    -+\t * [new reference]   HEAD -> refs/for/next/topic\n    -+\t * [new reference]   HEAD -> refs/review/a/b/c/topic\n    -+\t * [new reference]   HEAD -> refs/for/master/topic\n    ++\t * [new reference] HEAD -> refs/for/next/topic\n    ++\t * [new reference] HEAD -> refs/review/a/b/c/topic\n    ++\t * [new reference] HEAD -> refs/for/master/topic\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit show-ref\n    -+\t) >out &&\n    -+\tformat_git_output <out >actual &&\n    ++\tgit -C upstream show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n     +\tEOF\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"push with options\" '\n     +\tchmod a+x upstream/hooks/proc-receive\n     +'\n     +\n    -+test_expect_success \"report mixed refs update (head first)\" '\n    -+\t(\n    -+\t\tcd workbench &&\n    -+\t\tgit push origin \\\n    -+\t\t\tHEAD:refs/heads/zzz \\\n    -+\t\t\tHEAD:refs/for/next/topic \\\n    -+\t\t\tHEAD:refs/heads/yyy \\\n    -+\t\t\tHEAD:refs/for/master/topic\n    -+\t) >out 2>&1 &&\n    -+\tformat_git_output <out >actual &&\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n    ++test_expect_success \"report mixed refs update\" '\n    ++\tgit -C workbench push origin \\\n    ++\t\tHEAD:refs/heads/bar \\\n    ++\t\tHEAD:refs/heads/baz \\\n    ++\t\tHEAD:refs/for/next/topic \\\n    ++\t\tHEAD:refs/heads/foo \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: # pre-receive hook\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/zzz\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/yyy\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"push with options\" '\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n     +\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/zzz\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/yyy\n     +\tTo ../upstream\n    -+\t * [new branch]      HEAD -> zzz\n    -+\t * [new reference]   HEAD -> refs/for/next/topic\n    -+\t * [new branch]      HEAD -> yyy\n    -+\t * [new reference]   HEAD -> refs/for/master/topic\n    ++\t * [new branch] HEAD -> bar\n    ++\t * [new branch] HEAD -> baz\n    ++\t * [new reference] HEAD -> refs/for/next/topic\n    ++\t * [new branch] HEAD -> foo\n    ++\t * [new reference] HEAD -> refs/for/master/topic\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit show-ref\n    -+\t) >out &&\n    -+\tformat_git_output <out >actual &&\n    -+\tcat >expect <<-EOF &&\n    -+\t<COMMIT-A> refs/heads/master\n    -+\t<COMMIT-A> refs/heads/yyy\n    -+\t<COMMIT-A> refs/heads/zzz\n    -+\tEOF\n    -+\ttest_cmp expect actual\n    -+'\n    -+\n    -+test_expect_success \"cleanup\" '\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit update-ref -d refs/heads/yyy &&\n    -+\t\tgit update-ref -d refs/heads/zzz\n    -+\t)\n    -+'\n    -+\n    -+test_expect_success \"setup proc-receive hook\" '\n    -+\tcat >upstream/hooks/proc-receive <<-EOF &&\n    -+\t#!/bin/sh\n    -+\n    -+\tprintf >&2 \"# proc-receive hook\\n\"\n    -+\n    -+\ttest-tool proc-receive -v \\\n    -+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n    -+\t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n    -+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n    -+\tEOF\n    -+\tchmod a+x upstream/hooks/proc-receive\n    -+'\n    -+\n    -+test_expect_success \"report mixed refs update (special ref first)\" '\n    -+\t(\n    -+\t\tcd workbench &&\n    -+\t\tgit push origin \\\n    -+\t\t\tHEAD:refs/for/next/topic \\\n    -+\t\t\t$B:refs/heads/zzz \\\n    -+\t\t\tHEAD:refs/review/a/b/c/topic \\\n    -+\t\t\tHEAD:refs/heads/yyy \\\n    -+\t\t\tHEAD:refs/for/master/topic\n    -+\t) >out 2>&1 &&\n    -+\tformat_git_output <out >actual &&\n    -+\tcat >expect <<-EOF &&\n    -+\tremote: # pre-receive hook\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/heads/zzz\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/yyy\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: # proc-receive hook\n    -+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    -+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n    -+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n    -+\tremote: # post-receive hook\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/heads/zzz\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/yyy\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tTo ../upstream\n    -+\t * [new reference]   HEAD -> refs/for/next/topic\n    -+\t * [new branch]      <COMMIT-B> -> zzz\n    -+\t * [new reference]   HEAD -> refs/review/a/b/c/topic\n    -+\t * [new branch]      HEAD -> yyy\n    -+\t * [new reference]   HEAD -> refs/for/master/topic\n    -+\tEOF\n    -+\ttest_cmp expect actual &&\n    -+\t(\n    -+\t\tcd upstream &&\n    -+\t\tgit show-ref\n    -+\t) >out &&\n    -+\tformat_git_output <out >actual &&\n    ++\tgit -C upstream show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/bar\n    ++\t<COMMIT-A> refs/heads/baz\n    ++\t<COMMIT-A> refs/heads/foo\n     +\t<COMMIT-A> refs/heads/master\n    -+\t<COMMIT-A> refs/heads/yyy\n    -+\t<COMMIT-B> refs/heads/zzz\n     +\tEOF\n     +\ttest_cmp expect actual\n     +'\n5:  624538c1b7 < -:  ---------- receive-pack: refactor report for proc-receive\n-:  ---------- > 5:  439acbdb62 receive-pack: refactor report for proc-receive\n6:  56543573cf = 6:  24e5cddee0 doc: add documentation for the proc-receive hook\n\n\nJiang Xin (6):\n  transport: not report a non-head push as a branch\n  receive-pack: add new proc-receive hook\n  refs.c: refactor to reuse ref_is_hidden()\n  receive-pack: new config receive.procReceiveRefs\n  receive-pack: refactor report for proc-receive\n  doc: add documentation for the proc-receive hook\n\n Documentation/config/receive.txt |  14 +\n Documentation/githooks.txt       |  70 +++\n Makefile                         |   1 +\n builtin/receive-pack.c           | 290 +++++++++-\n refs.c                           |  11 +-\n refs.h                           |   1 +\n t/helper/test-proc-receive.c     | 172 ++++++\n t/helper/test-tool.c             |   1 +\n t/helper/test-tool.h             |   1 +\n t/t5411-proc-receive-hook.sh     | 916 +++++++++++++++++++++++++++++++\n t/t5516-fetch-push.sh            |   2 +-\n transport-helper.c               |  64 +--\n transport.c                      |  62 ++-\n 13 files changed, 1539 insertions(+), 66 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100755 t/t5411-proc-receive-hook.sh\n\n-- \n2.26.0.4.g39bcdcb101.dirty\n\n"},{"id":"394374","messageId":"20200330165754.19195-5-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v5 4/6] receive-pack: new config receive.procReceiveRefs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-30T16:57:52Z","receivedAt":"2020-03-30T16:58:11Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd a new multi-valued config variable \"receive.procReceiveRefs\"\nfor `receive-pack` command, like the follows:\n\n    git config --system --add receive.procReceiveRefs refs/for/\n    git config --system --add receive.procReceiveRefs refs/drafts/\n\nIf the specific prefix strings match the reference names of the commands\nwhich are sent by git client to `receive-pack`, these commands will be\nexecuted by an external hook (named \"proc-receive\"), instead of the\ninternal `execute_commands` function.\n\nFor example, if it is set to \"refs/for/\", pushing to a reference such as\n\"refs/for/master\" will not create or update reference \"refs/for/master\",\nbut may create or update a pull request directly by running the external\nhook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/config/receive.txt |  14 ++++\n builtin/receive-pack.c           |  43 ++++++++--\n t/t5411-proc-receive-hook.sh     | 130 +++++++++++++++++++++++++++++++\n 3 files changed, 180 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/receive.txt b/Documentation/config/receive.txt\nindex 65f78aac37..0178f2d478 100644\n--- a/Documentation/config/receive.txt\n+++ b/Documentation/config/receive.txt\n@@ -114,6 +114,20 @@ receive.hideRefs::\n \tAn attempt to update or delete a hidden ref by `git push` is\n \trejected.\n \n+receive.procReceiveRefs::\n+\tThis is a multi-valued variable that defines reference prefixes\n+\tto match the commands in `receive-pack`.  Commands matching the\n+\tprefixes will be executed by an external hooks \"proc-receive\",\n+\tinstead of the internal `execute_commands` function.  If this\n+\tvariable is not defined, the \"proc-receive\" hook will never be\n+\tused, and all commands will be executed by the internal\n+\t`execute_commands` function.\n+\n+\tFor example, if this variable is set to \"refs/for/\", pushing to\n+\treference such as \"refs/for/master\" will not create or update a\n+\treference named \"refs/for/master\", but may create or update a\n+\tpull request directly by running an external hook.\n+\n receive.updateServerInfo::\n \tIf set to true, git-receive-pack will run git-update-server-info\n \tafter receiving data from git-push and updating refs.\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 7dbb05df8c..21b3a1d3fa 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -76,6 +76,7 @@ static struct object_id push_cert_oid;\n static struct signature_check sigcheck;\n static const char *push_cert_nonce;\n static const char *cert_nonce_seed;\n+static struct string_list proc_receive_refs;\n \n static const char *NONCE_UNSOLICITED = \"UNSOLICITED\";\n static const char *NONCE_BAD = \"BAD\";\n@@ -228,6 +229,20 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (strcmp(var, \"receive.procreceiverefs\") == 0) {\n+\t\tchar *prefix;\n+\t\tint len;\n+\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tprefix = xstrdup(value);\n+\t\tlen = strlen(prefix);\n+\t\twhile (len && prefix[len - 1] == '/')\n+\t\t\tprefix[--len] = '\\0';\n+\t\tstring_list_insert(&proc_receive_refs, prefix);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -1710,15 +1725,26 @@ static void execute_commands(struct command *commands,\n \t/* Try to find commands that have special prefix in their reference names,\n \t * and mark them to run an external \"proc-receive\" hook later.\n \t */\n-\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n-\t\t\tcontinue;\n+\tif (proc_receive_refs.nr > 0) {\n+\t\tstruct strbuf refname_full = STRBUF_INIT;\n+\t\tsize_t prefix_len;\n+\n+\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n+\t\tprefix_len = refname_full.len;\n \n-\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n-\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n-\t\t\tcmd->run_proc_receive = 1;\n-\t\t\trun_proc_receive = 1;\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tif (!should_process_cmd(cmd))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_setlen(&refname_full, prefix_len);\n+\t\t\tstrbuf_addstr(&refname_full, cmd->ref_name);\n+\t\t\tif (ref_is_matched(&proc_receive_refs, cmd->ref_name, refname_full.buf)) {\n+\t\t\t\tcmd->run_proc_receive = 1;\n+\t\t\t\trun_proc_receive = 1;\n+\t\t\t}\n \t\t}\n+\n+\t\tstrbuf_release(&refname_full);\n \t}\n \n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n@@ -2178,6 +2204,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n+\tstring_list_init(&proc_receive_refs, 0);\n+\n \tpacket_trace_identity(\"receive-pack\");\n \n \targc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);\n@@ -2293,5 +2321,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \toid_array_clear(&shallow);\n \toid_array_clear(&ref);\n \tfree((void *)push_cert_nonce);\n+\tstring_list_clear(&proc_receive_refs, 0);\n \treturn 0;\n }\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex 79a3e7fde7..4c3699fa85 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -179,6 +179,14 @@ test_expect_success \"cleanup\" '\n \t)\n '\n \n+test_expect_success \"add two receive.procReceiveRefs settings\" '\n+\t(\n+\t\tcd upstream &&\n+\t\tgit config --add receive.procReceiveRefs refs/for/ &&\n+\t\tgit config --add receive.procReceiveRefs refs/review/\n+\t)\n+'\n+\n # Refs of upstream : master(A)\n # Refs of workbench: master(A)  tags/v123\n # git push         :                       next(A)  refs/for/master/topic(A)\n@@ -705,4 +713,126 @@ test_expect_success \"push with options\" '\n \ttest_cmp expect actual\n '\n \n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C upstream update-ref -d refs/heads/next\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+\tchmod a+x upstream/hooks/proc-receive\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/review/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"report update of all special refs\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/review/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo ../upstream\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/review/a/b/c/topic\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+\tchmod a+x upstream/hooks/proc-receive\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"report mixed refs update\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo ../upstream\n+\t * [new branch] HEAD -> bar\n+\t * [new branch] HEAD -> baz\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new branch] HEAD -> foo\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.26.0.4.g39bcdcb101.dirty\n\n"},{"id":"394376","messageId":"20200330165754.19195-6-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v5 5/6] receive-pack: refactor report for proc-receive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-30T16:57:53Z","receivedAt":"2020-03-30T16:58:12Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThe \"proc-receive\" may update one or more references, and will send its\nresult one by one in pkt-line format.  Each line of the result has four\nfields and one optional message field, as \"<old-oid> <new-oid> <ref>\n<status> [<message>]\".  See the following example:\n\n    # OK, run this command successfully.\n    PKT-LINE(old-oid new-oid ref ok)\n\n    # NO, I reject it.\n    PKT-LINE(old-oid new-oid ref ng reason)\n\n    # OK, but use an alternate reference.\n    PKT-LINE(old-oid new-oid ref ok ref:alt-ref)\n\n    # It will fallthrough to receive-pack to execute.\n    PKT-LINE(old-oid new-oid ref ft)\n\nThe first three fields have the same foramt as a command.\n\nThe forth field has a two-letter status code.  Available status code:\n\n* ok: The command runs successfully.  If the optional message has a\n  prefix \"ref:\", the hook has created/updated an alternate reference\n  instead.\n\n* ng: Fail to run the command. Error message is in the optional message\n  field.\n\n* ft: Will fallthrough to receive-pack to execute.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c       | 21 +++++++--\n t/t5411-proc-receive-hook.sh | 90 +++++++++++++++++++++++++++++++++---\n transport-helper.c           | 64 ++++++++++++-------------\n transport.c                  | 59 +++++++++++++----------\n 4 files changed, 168 insertions(+), 66 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 21b3a1d3fa..5e98eac3aa 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -328,6 +328,7 @@ static void write_head_info(void)\n struct command {\n \tstruct command *next;\n \tconst char *error_string;\n+\tconst char *extra_string;\n \tunsigned int skip_update:1,\n \t\t     did_not_exist:1,\n \t\t     run_proc_receive:2;\n@@ -906,7 +907,12 @@ static int read_proc_receive_result(struct packet_reader *reader,\n \t\t\telse\n \t\t\t\thint->error_string = \"failed\";\n \t\t\tcode = 1;\n-\t\t} else if (strcmp(\"ok\", status)) {\n+\t\t} else if (!strcmp(\"ok\", status)) {\n+\t\t\thint->extra_string = xstrdup_or_null(msg);\n+\t\t} else if (!strcmp(\"ft\", status)) {\n+\t\t\t/* Reset \"run_proc_receive\" field, and continue to run in \"receive-pack\" */\n+\t\t\thint->run_proc_receive = 0;\n+\t\t} else {\n \t\t\tdie(\"protocol error: proc-receive has bad status '%s' for '%s'\",\n \t\t\t    status, reader->line);\n \t\t}\n@@ -2161,12 +2167,17 @@ static void report(struct command *commands, const char *unpack_status)\n \tpacket_buf_write(&buf, \"unpack %s\\n\",\n \t\t\t unpack_status ? unpack_status : \"ok\");\n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!cmd->error_string)\n-\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n-\t\t\t\t\t cmd->ref_name);\n-\t\telse\n+\t\tif (!cmd->error_string) {\n+\t\t\tif (!cmd->extra_string)\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n+\t\t\t\t\t\t cmd->ref_name);\n+\t\t\telse\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s%c%s\\n\",\n+\t\t\t\t\t\t cmd->ref_name, ' ', cmd->extra_string);\n+\t\t} else {\n \t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n \t\t\t\t\t cmd->ref_name, cmd->error_string);\n+\t\t}\n \t}\n \tpacket_buf_flush(&buf);\n \ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex 4c3699fa85..119fd2fe59 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -726,9 +726,9 @@ test_expect_success \"setup proc-receive hook\" '\n \tprintf >&2 \"# proc-receive hook\\n\"\n \n \ttest-tool proc-receive -v \\\n-\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok ref:refs/pull/123/head\" \\\n \t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n-\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok ref:refs/pull/124/head\"\n \tEOF\n \tchmod a+x upstream/hooks/proc-receive\n '\n@@ -752,17 +752,17 @@ test_expect_success \"report update of all special refs\" '\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n-\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok ref:refs/pull/123/head\n \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n-\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok ref:refs/pull/124/head\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo ../upstream\n-\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \t * [new reference] HEAD -> refs/review/a/b/c/topic\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/pull/124/head\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C upstream show-ref >out &&\n@@ -791,6 +791,7 @@ test_expect_success \"setup proc-receive hook\" '\n # git push         :                       bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n test_expect_success \"report mixed refs update\" '\n \tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n \t\tHEAD:refs/heads/bar \\\n \t\tHEAD:refs/heads/baz \\\n \t\tHEAD:refs/for/next/topic \\\n@@ -800,6 +801,7 @@ test_expect_success \"report mixed refs update\" '\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n@@ -811,12 +813,14 @@ test_expect_success \"report mixed refs update\" '\n \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n \tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo ../upstream\n+\t <OID>..<OID> <COMMIT-B> -> master\n \t * [new branch] HEAD -> bar\n \t * [new branch] HEAD -> baz\n \t * [new reference] HEAD -> refs/for/next/topic\n@@ -830,6 +834,80 @@ test_expect_success \"report mixed refs update\" '\n \t<COMMIT-A> refs/heads/bar\n \t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"config receive.procReceiveRefs for all ref/\" '\n+\tgit -C upstream config --add receive.procReceiveRefs refs/\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$B $A refs/heads/master ft\" \\\n+\t\t-r \"$A $ZERO_OID refs/heads/foo ft\" \\\n+\t\t-r \"$A $B refs/heads/bar ft\" \\\n+\t\t-r \"$A $B refs/for/master/topic ok ref:refs/pull/123/head\" \\\n+\t\t-r \"$B $A refs/for/next/topic ok ref:refs/pull/124/head\"\n+\tEOF\n+\tchmod a+x upstream/hooks/proc-receive\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      :                       (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"report test: fallthrough\" '\n+\tgit -C workbench push -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/heads/master ft\n+\tremote: proc-receive> <COMMIT-A> <ZERO-OID> refs/heads/foo ft\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/heads/bar ft\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/for/master/topic ok ref:refs/pull/123/head\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/next/topic ok ref:refs/pull/124/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/next/topic\n+\tTo ../upstream\n+\t <OID>..<OID> <COMMIT-B> -> bar\n+\t - [deleted] foo\n+\t + <OID>...<OID> HEAD -> master (forced update)\n+\t * [new reference] HEAD -> refs/pull/123/head\n+\t * [new reference] HEAD -> refs/pull/124/head\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/master\n \tEOF\n \ttest_cmp expect actual\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 20a7185ec4..cec3495d59 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -747,37 +747,39 @@ static int push_update_ref_status(struct strbuf *buf,\n \t\t\tmsg = xstrdup(msg);\n \t\tstrbuf_release(&msg_buf);\n \n-\t\tif (!strcmp(msg, \"no match\")) {\n-\t\t\tstatus = REF_STATUS_NONE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"up to date\")) {\n-\t\t\tstatus = REF_STATUS_UPTODATE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"non-fast forward\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_NONFASTFORWARD;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"already exists\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_ALREADY_EXISTS;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"fetch first\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_FETCH_FIRST;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"needs force\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_NEEDS_FORCE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"stale info\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_STALE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"forced update\")) {\n-\t\t\tforced = 1;\n-\t\t\tFREE_AND_NULL(msg);\n+\t\tif (status != REF_STATUS_OK) {\n+\t\t\tif (!strcmp(msg, \"no match\")) {\n+\t\t\t\tstatus = REF_STATUS_NONE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"up to date\")) {\n+\t\t\t\tstatus = REF_STATUS_UPTODATE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"non-fast forward\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_NONFASTFORWARD;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"already exists\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_ALREADY_EXISTS;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"fetch first\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_FETCH_FIRST;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"needs force\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_NEEDS_FORCE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"stale info\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_STALE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"forced update\")) {\n+\t\t\t\tforced = 1;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n \t\t}\n \t}\n \ndiff --git a/transport.c b/transport.c\nindex 272c0f4046..28731fa014 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -459,15 +459,18 @@ void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int v\n \t}\n }\n \n-static void print_ref_status(char flag, const char *summary,\n+static void print_ref_status(char flag, const char *summary, char *target_refname,\n \t\t\t     struct ref *to, struct ref *from, const char *msg,\n \t\t\t     int porcelain, int summary_width)\n {\n+\tif (!target_refname)\n+\t\ttarget_refname = to->name;\n+\n \tif (porcelain) {\n \t\tif (from)\n-\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, target_refname);\n \t\telse\n-\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, target_refname);\n \t\tif (msg)\n \t\t\tfprintf(stdout, \"%s (%s)\\n\", summary, msg);\n \t\telse\n@@ -481,9 +484,9 @@ static void print_ref_status(char flag, const char *summary,\n \t\tfprintf(stderr, \" %s%c %-*s%s \", red, flag, summary_width,\n \t\t\tsummary, reset);\n \t\tif (from)\n-\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(to->name));\n+\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(target_refname));\n \t\telse\n-\t\t\tfputs(prettify_refname(to->name), stderr);\n+\t\t\tfputs(prettify_refname(target_refname), stderr);\n \t\tif (msg) {\n \t\t\tfputs(\" (\", stderr);\n \t\t\tfputs(msg, stderr);\n@@ -495,18 +498,26 @@ static void print_ref_status(char flag, const char *summary,\n \n static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_width)\n {\n+\tchar *refname;\n+\n+\tif (ref->remote_status && !strncmp(ref->remote_status, \"ref:\", 4))\n+\t\trefname = ref->remote_status + 4;\n+\telse\n+\t\trefname = ref->name;\n+\n \tif (ref->deletion)\n-\t\tprint_ref_status('-', \"[deleted]\", ref, NULL, NULL,\n+\t\tprint_ref_status('-', \"[deleted]\", refname, ref, NULL, NULL,\n \t\t\t\t porcelain, summary_width);\n-\telse if (is_null_oid(&ref->old_oid))\n+\telse if (is_null_oid(&ref->old_oid)) {\n+\n \t\tprint_ref_status('*',\n-\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t (starts_with(refname, \"refs/tags/\")\n \t\t\t\t  ? \"[new tag]\"\n-\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t  : (starts_with(refname, \"refs/heads/\")\n \t\t\t\t     ? \"[new branch]\"\n \t\t\t\t     : \"[new reference]\")),\n-\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n-\telse {\n+\t\t\t\t refname, ref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t} else {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n \t\tconst char *msg;\n@@ -525,7 +536,7 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\tstrbuf_add_unique_abbrev(&quickref, &ref->new_oid,\n \t\t\t\t\t DEFAULT_ABBREV);\n \n-\t\tprint_ref_status(type, quickref.buf, ref, ref->peer_ref, msg,\n+\t\tprint_ref_status(type, quickref.buf, refname, ref, ref->peer_ref, msg,\n \t\t\t\t porcelain, summary_width);\n \t\tstrbuf_release(&quickref);\n \t}\n@@ -542,56 +553,56 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count,\n \n \tswitch(ref->status) {\n \tcase REF_STATUS_NONE:\n-\t\tprint_ref_status('X', \"[no match]\", ref, NULL, NULL,\n+\t\tprint_ref_status('X', \"[no match]\", NULL, ref, NULL, NULL,\n \t\t\t\t porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NODELETE:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, NULL,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, NULL,\n \t\t\t\t \"remote does not support deleting refs\",\n \t\t\t\t porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_UPTODATE:\n-\t\tprint_ref_status('=', \"[up to date]\", ref,\n+\t\tprint_ref_status('=', \"[up to date]\", NULL, ref,\n \t\t\t\t ref->peer_ref, NULL, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NONFASTFORWARD:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"non-fast-forward\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"already exists\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_FETCH_FIRST:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"fetch first\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NEEDS_FORCE:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"needs force\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_STALE:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"stale info\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_SHALLOW:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"new shallow roots not allowed\",\n \t\t\t\t porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REMOTE_REJECT:\n-\t\tprint_ref_status('!', \"[remote rejected]\", ref,\n+\t\tprint_ref_status('!', \"[remote rejected]\", NULL, ref,\n \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n \t\t\t\t ref->remote_status, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_EXPECTING_REPORT:\n-\t\tprint_ref_status('!', \"[remote failure]\", ref,\n+\t\tprint_ref_status('!', \"[remote failure]\", NULL, ref,\n \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n \t\t\t\t \"remote failed to report status\",\n \t\t\t\t porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_ATOMIC_PUSH_FAILED:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"atomic push failed\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_OK:\n-- \n2.26.0.4.g39bcdcb101.dirty\n\n"},{"id":"394378","messageId":"20200330165754.19195-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v5 2/6] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-30T16:57:50Z","receivedAt":"2020-03-30T16:58:13Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nGit calls an internal `execute_commands` function to handle commands\nsent from client to `git-receive-pack`.  Regardless of what references\nthe user pushes, git creates or updates the corresponding references if\nthe user has write-permission.  A contributor who has no\nwrite-permission, cannot push to the repository directly.  So, the\ncontributor has to write commits to an alternate location, and sends\npull request by emails or by other ways.  We call this workflow as a\ndistributed workflow.\n\nIt would be more convenient to work in a centralized workflow like what\nGerrit provided for some cases.  For example, a read-only user who\ncannot push to a branch directly can run the following `git push`\ncommand to push commits to a pseudo reference (has a prefix \"refs/for/\",\nnot \"refs/heads/\") to create a code review.\n\n    git push origin \\\n        HEAD:refs/for/<branch-name>/<session>\n\nThe `<branch-name>` in the above example can be as simple as \"master\",\nor a more complicated branch name like \"foo/bar\".  The `<session>` in\nthe above example command can be the local branch name of the client\nside, such as \"my/topic\".\n\nWe cannot implement a centralized workflow elegantly by using\n\"pre-receive\" + \"post-receive\", because Git will call the internal\nfunction \"execute_commands\" to create references (even the special\npseudo reference) between these two hooks.  Even though we can delete\nthe temporarily created pseudo reference via the \"post-receive\" hook,\nhaving a temporary reference is not safe for concurrent pushes.\n\nSo, add a filter and a new handler to support this kind of workflow.\nThe filter will check the prefix of the reference name, and if the\ncommand has a special reference name, the filter will turn a specific\nfield (`run_proc_receive`) on for the command.  Commands with this filed\nturned on will be executed by a new handler (an hook named\n\"proc-receive\") instead of the internal `execute_commands` function.\nWe can use this \"proc-receive\" command to create pull requests or send\nemails for code review.\n\nSuggested by Junio, this \"proc-receive\" hook reads the commands,\npush-options (optional), and send result using a protocol in pkt-line\nformat.  In the following example, The letter \"S\" stands for\n\"receive-pack\" and letter \"H\" stands for the hook.\n\n    # Version and capabilities negotiation.\n    S: PKT-LINE(version=1\\0push-options atomic...)\n    S: flush-pkt\n    H: PKT-LINE(version=1\\0push-options...)\n    H: flush-pkt\n\n    # Send commands from server to the hook.\n    S: PKT-LINE(old-oid new-oid ref)\n    S: ... ...\n    S: flush-pkt\n    # Only if push-options have been negotiated.\n    S: PKT-LINE(push-option)\n    S: ... ...\n    S: flush-pkt\n\n    # Receive result from the hook.\n    # OK, run this command successfully.\n    H: PKT-LINE(old-oid new-oid ref ok)\n    # NO, I reject it.\n    H: PKT-LINE(old-oid new-oid ref ng reason)\n    # OK, but use an alternate reference. (in latter commit)\n    H: PKT-LINE(old-oid new-oid ref ok ref:alt-ref)\n    # It will fallthrough to receive-pack to execute. (in latter commit)\n    H: PKT-LINE(old-oid new-oid ref ft)\n    H: ... ...\n    H: flush-pkt\n\nAfter receiving a command, the hook can create/update another alternate\nreference.  For example, a command for a reference \"refs/for/master\" may\ncreate a special reference, such as \"refs/pull/123/head\".  The alternate\nreference can be returned from the result in an extensible format like\n\"<old-oid> <new-oid> <reference> <status> [<message>]\".\n\nThe result will be stored in a command list, and \"receive-pack\" will use\nthe result to replace the commands that have specific `run_proc_receive`\nfield turned on.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Makefile                     |   1 +\n builtin/receive-pack.c       | 242 ++++++++++++++-\n t/helper/test-proc-receive.c | 172 +++++++++++\n t/helper/test-tool.c         |   1 +\n t/helper/test-tool.h         |   1 +\n t/t5411-proc-receive-hook.sh | 555 +++++++++++++++++++++++++++++++++++\n 6 files changed, 969 insertions(+), 3 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n\ndiff --git a/Makefile b/Makefile\nindex 9804a0758b..0a08a0504e 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -725,6 +725,7 @@ TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\n+TEST_BUILTINS_OBJS += test-proc-receive.o\n TEST_BUILTINS_OBJS += test-progress.o\n TEST_BUILTINS_OBJS += test-reach.o\n TEST_BUILTINS_OBJS += test-read-cache.o\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 2cc18bbffd..7dbb05df8c 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -308,11 +308,14 @@ static void write_head_info(void)\n \tpacket_flush(1);\n }\n \n+#define RUN_PROC_RECEIVE_SCHEDULE\t1\n+#define RUN_PROC_RECEIVE_RETURNED\t2\n struct command {\n \tstruct command *next;\n \tconst char *error_string;\n \tunsigned int skip_update:1,\n-\t\t     did_not_exist:1;\n+\t\t     did_not_exist:1,\n+\t\t     run_proc_receive:2;\n \tint index;\n \tstruct object_id old_oid;\n \tstruct object_id new_oid;\n@@ -817,6 +820,212 @@ static int run_update_hook(struct command *cmd)\n \treturn finish_command(&proc);\n }\n \n+static struct command *find_command_by_refname(const struct command *list,\n+\t\t\t\t\t       const char *refname)\n+{\n+\tfor ( ; list; list = list->next)\n+\t\tif (!strcmp(list->ref_name, refname))\n+\t\t\treturn (struct command *)list;\n+\treturn NULL;\n+}\n+\n+static int read_proc_receive_result(struct packet_reader *reader,\n+\t\t\t\t    struct command *commands)\n+{\n+\tstruct command *hint;\n+\tstruct command *cmd;\n+\tint code = 0;\n+\n+\thint = NULL;\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\t\tchar *status;\n+\t\tchar *msg = NULL;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ')\n+\t\t\tdie(\"protocol error: proc-receive expected 'old new ref status [msg]', got '%s'\",\n+\t\t\t    reader->line);\n+\n+\t\trefname = p;\n+\t\tstatus = strchr(p, ' ');\n+\t\tif (!status)\n+\t\t\tdie(\"protocol error: proc-receive expected 'old new ref status [msg]', got '%s'\",\n+\t\t\t    reader->line);\n+\t\t*status++ = '\\0';\n+\t\tif (strlen(status) > 2 && *(status + 2) == ' ') {\n+\t\t\tmsg = status + 2;\n+\t\t\t*msg++ = '\\0';\n+\t\t}\n+\t\tif (strlen(status) != 2)\n+\t\t\tdie(\"protocol error: proc-receive has bad status '%s' for '%s'\",\n+\t\t\t    status, reader->line);\n+\n+\t\t/* first try searching at our hint, falling back to all refs */\n+\t\tif (hint)\n+\t\t\thint = find_command_by_refname(hint, refname);\n+\t\tif (!hint)\n+\t\t\thint = find_command_by_refname(commands, refname);\n+\t\tif (!hint) {\n+\t\t\twarning(\"proc-receive reported status on unknown ref: %s\",\n+\t\t\t\trefname);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!hint->run_proc_receive) {\n+\t\t\twarning(\"proc-receive reported status on ref of builtin command: %s\",\n+\t\t\t\trefname);\n+\t\t\tcontinue;\n+\t\t}\n+\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n+\t\toidcpy(&hint->old_oid, &old_oid);\n+\t\toidcpy(&hint->new_oid, &new_oid);\n+\t\tif (!strcmp(status, \"ng\")) {\n+\t\t\tif (msg)\n+\t\t\t\thint->error_string = xstrdup(msg);\n+\t\t\telse\n+\t\t\t\thint->error_string = \"failed\";\n+\t\t\tcode = 1;\n+\t\t} else if (strcmp(\"ok\", status)) {\n+\t\t\tdie(\"protocol error: proc-receive has bad status '%s' for '%s'\",\n+\t\t\t    status, reader->line);\n+\t\t}\n+\t}\n+\n+\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\tif (cmd->run_proc_receive &&\n+\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED))\n+\t\t    cmd->error_string = \"no report from proc-receive\";\n+\n+\treturn code;\n+}\n+\n+static int run_proc_receive_hook(struct command *commands,\n+\t\t\t\t const struct string_list *push_options)\n+{\n+\tstruct child_process proc = CHILD_PROCESS_INIT;\n+\tstruct async muxer;\n+\tstruct command *cmd;\n+\tconst char *argv[2];\n+\tstruct packet_reader reader;\n+\tstruct strbuf cap = STRBUF_INIT;\n+\tint pr_use_push_options = 0;\n+\tint version = 0;\n+\tint code;\n+\n+\targv[0] = find_hook(\"proc-receive\");\n+\tif (!argv[0]) {\n+\t\trp_error(\"cannot to find hook 'proc-receive'\");\n+\t\treturn 1;\n+\t}\n+\targv[1] = NULL;\n+\n+\tproc.argv = argv;\n+\tproc.in = -1;\n+\tproc.out = -1;\n+\tproc.trace2_hook_name = \"proc-receive\";\n+\n+\tif (use_sideband) {\n+\t\tmemset(&muxer, 0, sizeof(muxer));\n+\t\tmuxer.proc = copy_to_sideband;\n+\t\tmuxer.in = -1;\n+\t\tcode = start_async(&muxer);\n+\t\tif (code)\n+\t\t\treturn code;\n+\t\tproc.err = muxer.in;\n+\t} else {\n+\t\tproc.err = 0;\n+\t}\n+\n+\tcode = start_command(&proc);\n+\tif (code) {\n+\t\tif (use_sideband)\n+\t\t\tfinish_async(&muxer);\n+\t\treturn code;\n+\t}\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\n+\t/* Version negotiaton */\n+\tpacket_reader_init(&reader, proc.out, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\tif (use_atomic)\n+\t\tstrbuf_addstr(&cap, \" atomic\");\n+\tif (use_push_options)\n+\t\tstrbuf_addstr(&cap, \" push-options\");\n+\tif (cap.len) {\n+\t\tpacket_write_fmt(proc.in, \"version=1%c%s\\n\", '\\0', cap.buf + 1);\n+\t\tstrbuf_release(&cap);\n+\t} else {\n+\t\tpacket_write_fmt(proc.in, \"version=1\\n\");\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader.pktlen > 8 && starts_with(reader.line, \"version=\")) {\n+\t\t\tversion = atoi(reader.line + 8);\n+\t\t\tlinelen = strlen(reader.line);\n+\t\t\tif (linelen < reader.pktlen) {\n+\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tpr_use_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (version != 1)\n+\t\tdie(\"protocol error: unknown proc-receive version '%d'\", version);\n+\n+\t/* Send commands */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tchar *old_hex, *new_hex;\n+\n+\t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)\n+\t\t\tcontinue;\n+\n+\t\told_hex = oid_to_hex(&cmd->old_oid);\n+\t\tnew_hex = oid_to_hex(&cmd->new_oid);\n+\n+\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n+\t\t\t\t old_hex, new_hex, cmd->ref_name);\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\t/* Send push options */\n+\tif (pr_use_push_options) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tfor_each_string_list_item(item, push_options)\n+\t\t\tpacket_write_fmt(proc.in, \"%s\", item->string);\n+\n+\t\tpacket_flush(proc.in);\n+\t}\n+\n+\t/* Read result from proc-receive */\n+\tcode = read_proc_receive_result(&reader, commands);\n+\tclose(proc.in);\n+\tclose(proc.out);\n+\tif (use_sideband)\n+\t\tfinish_async(&muxer);\n+\tif (finish_command(&proc))\n+\t\tdie(\"proc-receive did not exit properly\");\n+\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn code;\n+}\n+\n static char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n@@ -1392,7 +1601,7 @@ static void execute_commands_non_atomic(struct command *commands,\n \tstruct strbuf err = STRBUF_INIT;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\ttransaction = ref_transaction_begin(&err);\n@@ -1432,7 +1641,7 @@ static void execute_commands_atomic(struct command *commands,\n \t}\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\tcmd->error_string = update(cmd, si);\n@@ -1468,6 +1677,7 @@ static void execute_commands(struct command *commands,\n \tstruct iterate_data data;\n \tstruct async muxer;\n \tint err_fd = 0;\n+\tint run_proc_receive = 0;\n \n \tif (unpacker_error) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n@@ -1497,6 +1707,20 @@ static void execute_commands(struct command *commands,\n \n \treject_updates_to_hidden(commands);\n \n+\t/* Try to find commands that have special prefix in their reference names,\n+\t * and mark them to run an external \"proc-receive\" hook later.\n+\t */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!should_process_cmd(cmd))\n+\t\t\tcontinue;\n+\n+\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n+\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n+\t\t\tcmd->run_proc_receive = 1;\n+\t\t\trun_proc_receive = 1;\n+\t\t}\n+\t}\n+\n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n \t\t\tif (!cmd->error_string)\n@@ -1523,6 +1747,18 @@ static void execute_commands(struct command *commands,\n \tfree(head_name_to_free);\n \thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, NULL, NULL);\n \n+\tif (run_proc_receive) {\n+\t\tint code;\n+\n+\t\tcode = run_proc_receive_hook(commands, push_options);\n+\t\tif (code) {\n+\t\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\t\tif (!cmd->error_string  && (cmd->run_proc_receive || use_atomic))\n+\t\t\t\t\tcmd->error_string = \"fail to run proc-receive hook\";\n+\t\t\t}\n+\t\t}\n+\t}\n+\n \tif (use_atomic)\n \t\texecute_commands_atomic(commands, si);\n \telse\ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nnew file mode 100644\nindex 0000000000..aafa3e8e59\n--- /dev/null\n+++ b/t/helper/test-proc-receive.c\n@@ -0,0 +1,172 @@\n+#include \"cache.h\"\n+#include \"connect.h\"\n+#include \"parse-options.h\"\n+#include \"pkt-line.h\"\n+#include \"string-list.h\"\n+#include \"test-tool.h\"\n+\n+static const char *proc_receive_usage[] = {\n+\t\"test-tool proc-receive [<options>...]\",\n+\tNULL\n+};\n+\n+static int version = 1;\n+static int verbose = 0;\n+static int no_push_options = 0;\n+static int use_atomic = 0;\n+static int use_push_options = 0;\n+static struct string_list returns = STRING_LIST_INIT_NODUP;\n+\n+struct command {\n+\tstruct command *next;\n+\tconst char *error_string;\n+\tunsigned int skip_update:1,\n+\t\t     did_not_exist:1;\n+\tint index;\n+\tstruct object_id old_oid;\n+\tstruct object_id new_oid;\n+\tchar ref_name[FLEX_ARRAY]; /* more */\n+};\n+\n+static void proc_receive_verison(struct packet_reader *reader) {\n+\tint server_version = 0;\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader->pktlen > 8 && starts_with(reader->line, \"version=\")) {\n+\t\t\tserver_version = atoi(reader->line+8);\n+\t\t\tlinelen = strlen(reader->line);\n+\t\t\tif (linelen < reader->pktlen) {\n+\t\t\t\tconst char *feature_list = reader->line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"atomic\"))\n+\t\t\t\t\tuse_atomic= 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tuse_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (server_version != 1)\n+\t\tdie(\"bad protocol version: %d\", server_version);\n+\n+\tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n+\t\t\t version, '\\0',\n+\t\t\t use_push_options && !no_push_options ? \"push-options\": \"\");\n+\tpacket_flush(1);\n+}\n+\n+static void proc_receive_read_commands(struct packet_reader *reader,\n+\t\t\t\t       struct command **commands)\n+{\n+\tstruct command **tail = commands;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tstruct command *cmd;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ')\n+\t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n+\t\t\t    reader->line);\n+\t\trefname = p;\n+\t\tFLEX_ALLOC_MEM(cmd, ref_name, refname, strlen(refname));\n+\t\toidcpy(&cmd->old_oid, &old_oid);\n+\t\toidcpy(&cmd->new_oid, &new_oid);\n+\n+\t\t*tail = cmd;\n+\t\ttail = &cmd->next;\n+\t}\n+}\n+\n+static void proc_receive_read_push_options(struct packet_reader *reader,\n+\t\t\t\t\t   struct string_list *options)\n+{\n+\n+\tif (no_push_options || !use_push_options)\n+\t       return;\n+\n+\twhile (1) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tstring_list_append(options, reader->line);\n+\t}\n+}\n+\n+int cmd__proc_receive(int argc, const char **argv)\n+{\n+\tstruct packet_reader reader;\n+\tstruct command *commands;\n+\tstruct string_list push_options = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"no-push-options\", &no_push_options,\n+\t\t\t \"disable push options\"),\n+\t\tOPT_STRING_LIST('r', \"return\", &returns, \"old/new/ref/status/msg\",\n+\t\t\t\t\"return of results\"),\n+\t\tOPT__VERBOSE(&verbose, \"be verbose\"),\n+\t\tOPT_INTEGER('V', \"version\", &version,\n+\t\t\t    \"use this protocol version number\"),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, \"test-tools\", options, proc_receive_usage, 0);\n+\tif (argc > 0)\n+\t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n+\n+\tpacket_reader_init(&reader, 0, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tproc_receive_verison(&reader);\n+\tproc_receive_read_commands(&reader, &commands);\n+\tproc_receive_read_push_options(&reader, &push_options);\n+\n+\tif (verbose) {\n+\t\tstruct command *cmd;\n+\n+\t\tif (use_push_options || use_atomic)\n+\t\t\tfprintf(stderr, \"proc-receive:%s%s\\n\",\n+\t\t\t\tuse_atomic? \" atomic\": \"\",\n+\t\t\t\tuse_push_options ? \" push_options\": \"\");\n+\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tchar *old_hex, *new_hex;\n+\n+\t\t\told_hex = oid_to_hex(&cmd->old_oid);\n+\t\t\tnew_hex = oid_to_hex(&cmd->new_oid);\n+\t\t\tfprintf(stderr, \"proc-receive< %s %s %s\\n\",\n+\t\t\t\told_hex, new_hex, cmd->ref_name);\n+\t\t}\n+\n+\t\tif (push_options.nr > 0) {\n+\t\t\tfor_each_string_list_item(item, &push_options)\n+\t\t\t\tfprintf(stderr, \"proc-receive< %s\\n\", item->string);\n+\t\t}\n+\n+\t\tif (returns.nr) {\n+\t\t\tfor_each_string_list_item(item, &returns)\n+\t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n+\t\t}\n+\t}\n+\n+\tif (returns.nr) {\n+\t\tfor_each_string_list_item(item, &returns)\n+\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n+\t}\n+\tpacket_flush(1);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex c9a232d238..9bff461446 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -43,6 +43,7 @@ static struct test_cmd cmds[] = {\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\n+\t{ \"proc-receive\", cmd__proc_receive},\n \t{ \"progress\", cmd__progress },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex c8549fd87f..71e5f1c372 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -33,6 +33,7 @@ int cmd__parse_pathspec_file(int argc, const char** argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\n+int cmd__proc_receive(int argc, const char **argv);\n int cmd__progress(int argc, const char **argv);\n int cmd__reach(int argc, const char **argv);\n int cmd__read_cache(int argc, const char **argv);\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex 1784bcb584..79a3e7fde7 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -57,6 +57,24 @@ make_user_friendly_and_stable_output () {\n \t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\"\n }\n \n+# Asynchronous sideband may generate inconsistent output messages,\n+# sort before comparison.\n+test_sorted_cmp () {\n+\tif ! $GIT_TEST_CMP \"$@\" >/dev/null 2>&1\n+\tthen\n+\t\tcmd=$GIT_TEST_CMP\n+\t\tfor f in \"$@\"\n+\t\tdo\n+\t\t\tsort \"$f\" >\"$f.sorted\"\n+\t\t\tcmd=\"$cmd \\\"$f.sorted\\\"\"\n+\t\tdone\n+\t\tif ! eval $cmd >/dev/null 2>&1\n+\t\tthen\n+\t\t\t$GIT_TEST_CMP \"$@\"\n+\t\tfi\n+\tfi\n+}\n+\n # Refs of upstream : master(B)  next(A)\n # Refs of workbench: master(A)           tags/v123\n test_expect_success \"setup\" '\n@@ -150,4 +168,541 @@ test_expect_success \"normal git-push command\" '\n \ttest_cmp expect actual\n '\n \n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\t(\n+\t\tcd upstream &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"no proc-receive hook, fail to push special ref\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot to find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo ../upstream\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\terror: failed to push some refs to \"../upstream\"\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C upstream update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic:                       next(A)  refs/for/master/topic(A)\n+test_expect_failure \"no proc-receive hook, fail all for atomic push\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot to find hook \"proc-receive\"\n+\tTo ../upstream\n+\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\terror: failed to push some refs to \"../upstream\"\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad version)\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+\tchmod a+x upstream/hooks/proc-receive\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive bad protocol: unknown version\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out | grep \"protocol error\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tfatal: protocol error: unknown proc-receive version \"2\"\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report)\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive bad protocol: no report\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo ../upstream\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\terror: failed to push some refs to \"../upstream\"\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C upstream update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad oid)\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"bad-id new-id ref ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: bad oid\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out | grep \"protocol error\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tfatal: protocol error: proc-receive expected \"old new ref status [msg]\", got \"bad-id new-id ref ok\"\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no status)\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: no status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out | grep \"protocol error\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tfatal: protocol error: proc-receive expected \"old new ref status [msg]\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status)\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic xx msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: unknown status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out | grep \"protocol error\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tfatal: protocol error: proc-receive has bad status \"xx\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad status)\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic bad status\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: bad status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out | grep \"protocol error\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tfatal: protocol error: proc-receive has bad status \"bad status\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng)\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (no message)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng\n+\tTo ../upstream\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n+\terror: failed to push some refs to \"../upstream\"\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message)\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (has message)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng error msg\n+\tTo ../upstream\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n+\terror: failed to push some refs to \"../upstream\"\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (report status on builtin command)\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/heads/master ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: warning on report for builtin command\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/heads/master ok\n+\twarning: proc-receive reported status on ref of builtin command: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo ../upstream\n+\t <OID>..<OID> <COMMIT-B> -> master\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\terror: failed to push some refs to \"../upstream\"\n+\tEOF\n+\ttest_sorted_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup\" '\n+\tgit -C upstream update-ref refs/heads/master $A\n+'\n+\n+test_expect_success \"setup proc-receive hook (ok)\" '\n+\tcat >upstream/hooks/proc-receive <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo ../upstream\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: no report from proc-receive\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\twarning: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo ../upstream\n+\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (no report from proc-receive)\n+\terror: failed to push some refs to \"../upstream\"\n+\tEOF\n+\ttest_sorted_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"not support push options\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tfatal: the receiving end does not support push options\n+\tfatal: the remote end hung up unexpectedly\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options\" '\n+\tgit -C upstream config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"push with options\" '\n+\tgit -C workbench push \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo ../upstream\n+\t * [new branch] HEAD -> next\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.26.0.4.g39bcdcb101.dirty\n\n"},{"id":"394377","messageId":"20200330165754.19195-7-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v5 6/6] doc: add documentation for the proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-30T16:57:54Z","receivedAt":"2020-03-30T16:58:14Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd documentation for the new \"proc-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/githooks.txt | 70 ++++++++++++++++++++++++++++++++++++++\n 1 file changed, 70 insertions(+)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 3dccab5375..10ea5c1f18 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -333,6 +333,76 @@ The default 'update' hook, when enabled--and with\n `hooks.allowunannotated` config option unset or set to false--prevents\n unannotated tags to be pushed.\n \n+[[proc-receive]]\n+proc-receive\n+~~~~~~~~~~~~\n+This hook is invoked by linkgit:git-receive-pack[1] when it reacts to\n+special `git push` command.  According to refnames of the commands which\n+`git push` sends to 'git-receive-pack', the commands will be devided\n+into two groups by matching what the `receive.procReceiveRefs`\n+configuration variable defines.  One group of the commands will execute\n+the internal `execute_commands` function to update the corresponding\n+refnames, and the other group of commands which have matching refnames\n+will execute this 'proc-receive' hook to create pull requests, etc.\n+If there is no `receive.procReceiveRefs` settings, this hook won't\n+execute at all, and all commands are sent to the internal\n+`execute_commands` function.\n+\n+Its exit status only determines the success or failure of the group of\n+commands with special refnames, unless atomic push is in use.\n+\n+This hook executes once for the receive operation.  It takes no\n+arguments, but will talk a protocol in pkt-line format with the\n+'receive-pack' for reading commands, push-options (optional), and\n+sending result.  In the following example, The letter \"S\" stands for\n+\"receive-pack\" and letter \"H\" stands for the hook.\n+\n+    # Version and capabilities negotiation.\n+    S: PKT-LINE(version=1\\0push-options atomic...)\n+    S: flush-pkt\n+    H: PKT-LINE(version=1\\0push-options...)\n+    H: flush-pkt\n+\n+    # Send commands from server to the hook.\n+    S: PKT-LINE(old-oid new-oid ref)\n+    S: ... ...\n+    S: flush-pkt\n+    # Only if push-options have been negotiated.\n+    S: PKT-LINE(push-option)\n+    S: ... ...\n+    S: flush-pkt\n+\n+    # Receive result from the hook.\n+    # OK, run this command successfully.\n+    H: PKT-LINE(old-oid new-oid ref ok)\n+    # NO, I reject it.\n+    H: PKT-LINE(old-oid new-oid ref ng reason)\n+    # OK, but use an alternate reference.\n+    H: PKT-LINE(old-oid new-oid ref ok ref:alt-ref)\n+    # It will fallthrough to receive-pack to execute.\n+    H: PKT-LINE(old-oid new-oid ref ft)\n+    H: ... ...\n+    H: flush-pkt\n+\n+The \"proc-receive\" hook may update one or more references, and will send\n+its result one by one in pkt-line format.  Each line of the result has\n+four fields and one optional message field, like \"<old-oid> <new-oid>\n+<ref> <status> [<message>]\".\n+\n+The first three fields are the same as those of the commands for\n+\"receive-pack\".\n+\n+The forth field has a two-letter status code.  Available status codes:\n+\n+* ok: The command runs successfully.  If the optional message has a\n+  prefix \"ref:\", the hook has created/updated an alternate reference\n+  instead.\n+\n+* ng: Fail to run the command.  Error message is given in the optional\n+  message field.\n+\n+* ft: Will fallthrough to receive-pack to execute.\n+\n [[post-receive]]\n post-receive\n ~~~~~~~~~~~~\n-- \n2.26.0.4.g39bcdcb101.dirty\n\n"},{"id":"394379","messageId":"20200330165754.19195-4-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v5 3/6] refs.c: refactor to reuse ref_is_hidden()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-03-30T16:57:51Z","receivedAt":"2020-03-30T16:58:15Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd new function `ref_is_matched()` to reuse `ref_is_hidden()`. Will use\nthis function for `receive-pack` to check commands with specific\nprefixes.\n\nTest case t5512 covered this change.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n refs.c | 11 ++++++++---\n refs.h |  1 +\n 2 files changed, 9 insertions(+), 3 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 1ab0bb54d3..229159ea1a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1389,13 +1389,18 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti\n }\n \n int ref_is_hidden(const char *refname, const char *refname_full)\n+{\n+\treturn ref_is_matched(hide_refs, refname, refname_full);\n+}\n+\n+int ref_is_matched(struct string_list *match_refs, const char *refname, const char *refname_full)\n {\n \tint i;\n \n-\tif (!hide_refs)\n+\tif (!match_refs)\n \t\treturn 0;\n-\tfor (i = hide_refs->nr - 1; i >= 0; i--) {\n-\t\tconst char *match = hide_refs->items[i].string;\n+\tfor (i = match_refs->nr - 1; i >= 0; i--) {\n+\t\tconst char *match = match_refs->items[i].string;\n \t\tconst char *subject;\n \t\tint neg = 0;\n \t\tconst char *p;\ndiff --git a/refs.h b/refs.h\nindex 545029c6d8..a2ea043f7f 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -739,6 +739,7 @@ int parse_hide_refs_config(const char *var, const char *value, const char *);\n  * parameter always points to the full ref name.\n  */\n int ref_is_hidden(const char *, const char *);\n+int ref_is_matched(struct string_list *, const char *, const char *);\n \n enum ref_type {\n \tREF_TYPE_PER_WORKTREE,\t  /* refs inside refs/ but not shared       */\n-- \n2.26.0.4.g39bcdcb101.dirty\n\n"},{"id":"394398","messageId":"xmqqy2rh74w6.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200330165754.19195-3-worldhello.net@gmail.com","subject":"Re: [PATCH v5 2/6] receive-pack: add new proc-receive hook","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-03-31T00:19:05Z","receivedAt":"2020-03-31T00:19:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> +\targv[0] = find_hook(\"proc-receive\");\n> +\tif (!argv[0]) {\n> +\t\trp_error(\"cannot to find hook 'proc-receive'\");\n\nPerhaps discard \"to\" from here.  Also I notice that other messages\ngiven to rp_error() are marked for _(i18n), but this one is not.\n\n> +\tmake_user_friendly_and_stable_output <out >actual &&\n> +\tcat >expect <<-EOF &&\n> +\tremote: # pre-receive hook\n> +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n> +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n> +\tremote: error: cannot to find hook \"proc-receive\"\n> +\tremote: # post-receive hook\n> +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n> +\tTo ../upstream\n> +\t * [new branch] HEAD -> next\n> +\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n> +\terror: failed to push some refs to \"../upstream\"\n> +\tEOF\n> +\ttest_cmp expect actual &&\n\nThis breaks gettext-poison test.  \n\nSee https://travis-ci.org/github/git/git/jobs/669008025 for example.\n\n"},{"id":"394399","messageId":"xmqqtv2574rk.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200330165754.19195-3-worldhello.net@gmail.com","subject":"Re: [PATCH v5 2/6] receive-pack: add new proc-receive hook","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-03-31T00:21:51Z","receivedAt":"2020-03-31T00:22:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> diff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\n> new file mode 100644\n> index 0000000000..aafa3e8e59\n> --- /dev/null\n> +++ b/t/helper/test-proc-receive.c\n> @@ -0,0 +1,172 @@\n> +static void proc_receive_verison(struct packet_reader *reader) {\n\nIs that a phone company ;-)?\n\n> +\tint server_version = 0;\n> +\n> +\tfor (;;) {\n> +...\n> +static void proc_receive_read_commands(struct packet_reader *reader,\n> +\t\t\t\t       struct command **commands)\n> +{\n> +\tstruct command **tail = commands;\n> +\n> +\tfor (;;) {\n> +\t\tstruct object_id old_oid, new_oid;\n> +\t\tstruct command *cmd;\n> +\t\tconst char *refname;\n> +\t\tconst char *p;\n> +\n> +\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n> +\t\t\tbreak;\n> +\t\t}\n> +\n> +\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n> +\t\t    *p++ != ' ' ||\n> +\t\t    parse_oid_hex(p, &new_oid, &p) ||\n> +\t\t    *p++ != ' ')\n> +\t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n> +\t\t\t    reader->line);\n> +\t\trefname = p;\n> +\t\tFLEX_ALLOC_MEM(cmd, ref_name, refname, strlen(refname));\n\n\t\tFLEX_ALLOC_STR(cmd, ref_name, refname);\n\n> +\t\toidcpy(&cmd->old_oid, &old_oid);\n> +\t\toidcpy(&cmd->new_oid, &new_oid);\n> +\n> +\t\t*tail = cmd;\n> +\t\ttail = &cmd->next;\n> +\t}\n> +}\n"},{"id":"394595","messageId":"20200402163520.1338-4-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v6 3/7] refs.c: refactor to reuse ref_is_hidden()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-02T16:35:16Z","receivedAt":"2020-04-02T16:35:37Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd new function `ref_is_matched()` to reuse `ref_is_hidden()`. Will use\nthis function for `receive-pack` to check commands with specific\nprefixes.\n\nTest case t5512 covered this change.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n refs.c | 11 ++++++++---\n refs.h |  1 +\n 2 files changed, 9 insertions(+), 3 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 1ab0bb54d3..229159ea1a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1389,13 +1389,18 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti\n }\n \n int ref_is_hidden(const char *refname, const char *refname_full)\n+{\n+\treturn ref_is_matched(hide_refs, refname, refname_full);\n+}\n+\n+int ref_is_matched(struct string_list *match_refs, const char *refname, const char *refname_full)\n {\n \tint i;\n \n-\tif (!hide_refs)\n+\tif (!match_refs)\n \t\treturn 0;\n-\tfor (i = hide_refs->nr - 1; i >= 0; i--) {\n-\t\tconst char *match = hide_refs->items[i].string;\n+\tfor (i = match_refs->nr - 1; i >= 0; i--) {\n+\t\tconst char *match = match_refs->items[i].string;\n \t\tconst char *subject;\n \t\tint neg = 0;\n \t\tconst char *p;\ndiff --git a/refs.h b/refs.h\nindex 545029c6d8..a2ea043f7f 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -739,6 +739,7 @@ int parse_hide_refs_config(const char *var, const char *value, const char *);\n  * parameter always points to the full ref name.\n  */\n int ref_is_hidden(const char *, const char *);\n+int ref_is_matched(struct string_list *, const char *, const char *);\n \n enum ref_type {\n \tREF_TYPE_PER_WORKTREE,\t  /* refs inside refs/ but not shared       */\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394596","messageId":"20200402163520.1338-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v6 1/7] transport: not report a non-head push as a branch","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-02T16:35:14Z","receivedAt":"2020-04-02T16:35:37Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing a new reference (not a head or tag), report it as a new\nreference instead of a new branch.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411-proc-receive-hook.sh | 153 +++++++++++++++++++++++++++++++++++\n t/t5516-fetch-push.sh        |   2 +-\n transport.c                  |   9 ++-\n 3 files changed, 160 insertions(+), 4 deletions(-)\n create mode 100755 t/t5411-proc-receive-hook.sh\n\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nnew file mode 100755\nindex 0000000000..1784bcb584\n--- /dev/null\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -0,0 +1,153 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook'\n+\n+. ./test-lib.sh\n+\n+# Create commits in <repo> and assign each commit's oid to shell variables\n+# given in the arguments (A, B, and C). E.g.:\n+#\n+#     create_commits_in <repo> A B C\n+#\n+# NOTE: Never calling this function from a subshell since variable\n+# assignments will disappear when subshell exits.\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{})\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  We can easily prepare the expect text\n+# without having to worry about future changes of the commit ID and spaces\n+# of the output.  We also replce single quotes with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect txt.\n+make_user_friendly_and_stable_output () {\n+\tsed \\\n+\t\t-e \"s/  *\\$//\" \\\n+\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/'/\\\"/g\" \\\n+\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n+\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n+\t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n+\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n+\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\"\n+}\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+test_expect_success \"setup\" '\n+\tgit init --bare upstream &&\n+\tgit init workbench &&\n+\tcreate_commits_in workbench A B &&\n+\t(\n+\t\tcd workbench &&\n+\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\tgit config core.abbrev 7 &&\n+\t\tgit remote add origin ../upstream &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit tag -m \"v123\" v123 $A &&\n+\t\tgit push origin \\\n+\t\t\t$B:refs/heads/master \\\n+\t\t\t$A:refs/heads/next\n+\t) &&\n+\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t# setup pre-receive hook\n+\tcat >upstream/hooks/pre-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# pre-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"pre-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\t# setup post-receive hook\n+\tcat >upstream/hooks/post-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# post-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"post-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\tchmod a+x \\\n+\t\tupstream/hooks/pre-receive \\\n+\t\tupstream/hooks/post-receive\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"normal git-push command\" '\n+\tgit -C workbench push -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tHEAD:refs/heads/master \\\n+\t\tHEAD:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo ../upstream\n+\t + <OID>...<OID> HEAD -> master (forced update)\n+\t - [deleted] next\n+\t * [new tag] v123 -> v123\n+\t * [new reference] HEAD -> refs/review/master/topic\n+\t * [new branch] HEAD -> a/b/c\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 9ff041a093..9e4b9313b5 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1039,7 +1039,7 @@ test_force_fetch_tag \"annotated tag\" \"-f -a -m'tag message'\"\n test_expect_success 'push --porcelain' '\n \tmk_empty testrepo &&\n \techo >.git/foo  \"To testrepo\" &&\n-\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new branch]\"  &&\n+\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new reference]\"  &&\n \techo >>.git/foo \"Done\" &&\n \tgit push >.git/bar --porcelain  testrepo refs/heads/master:refs/remotes/origin/master &&\n \t(\ndiff --git a/transport.c b/transport.c\nindex 1fdc7dac1a..272c0f4046 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -500,9 +500,12 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\t\t\t porcelain, summary_width);\n \telse if (is_null_oid(&ref->old_oid))\n \t\tprint_ref_status('*',\n-\t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n-\t\t\t\"[new branch]\"),\n-\t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t  ? \"[new tag]\"\n+\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t     ? \"[new branch]\"\n+\t\t\t\t     : \"[new reference]\")),\n+\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394597","messageId":"20200402163520.1338-5-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v6 4/7] receive-pack: new config receive.procReceiveRefs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-02T16:35:17Z","receivedAt":"2020-04-02T16:35:38Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd a new multi-valued config variable \"receive.procReceiveRefs\"\nfor `receive-pack` command, like the follows:\n\n    git config --system --add receive.procReceiveRefs refs/for/\n    git config --system --add receive.procReceiveRefs refs/drafts/\n\nIf the specific prefix strings match the reference names of the commands\nwhich are sent by git client to `receive-pack`, these commands will be\nexecuted by an external hook (named \"proc-receive\"), instead of the\ninternal `execute_commands` function.\n\nFor example, if it is set to \"refs/for/\", pushing to a reference such as\n\"refs/for/master\" will not create or update reference \"refs/for/master\",\nbut may create or update a pull request directly by running the external\nhook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/config/receive.txt |  14 ++++\n builtin/receive-pack.c           |  43 ++++++++--\n t/t5411-proc-receive-hook.sh     | 130 +++++++++++++++++++++++++++++++\n 3 files changed, 180 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/receive.txt b/Documentation/config/receive.txt\nindex 65f78aac37..0178f2d478 100644\n--- a/Documentation/config/receive.txt\n+++ b/Documentation/config/receive.txt\n@@ -114,6 +114,20 @@ receive.hideRefs::\n \tAn attempt to update or delete a hidden ref by `git push` is\n \trejected.\n \n+receive.procReceiveRefs::\n+\tThis is a multi-valued variable that defines reference prefixes\n+\tto match the commands in `receive-pack`.  Commands matching the\n+\tprefixes will be executed by an external hooks \"proc-receive\",\n+\tinstead of the internal `execute_commands` function.  If this\n+\tvariable is not defined, the \"proc-receive\" hook will never be\n+\tused, and all commands will be executed by the internal\n+\t`execute_commands` function.\n+\n+\tFor example, if this variable is set to \"refs/for/\", pushing to\n+\treference such as \"refs/for/master\" will not create or update a\n+\treference named \"refs/for/master\", but may create or update a\n+\tpull request directly by running an external hook.\n+\n receive.updateServerInfo::\n \tIf set to true, git-receive-pack will run git-update-server-info\n \tafter receiving data from git-push and updating refs.\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 45f2d426fc..3b2f949255 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -76,6 +76,7 @@ static struct object_id push_cert_oid;\n static struct signature_check sigcheck;\n static const char *push_cert_nonce;\n static const char *cert_nonce_seed;\n+static struct string_list proc_receive_refs;\n \n static const char *NONCE_UNSOLICITED = \"UNSOLICITED\";\n static const char *NONCE_BAD = \"BAD\";\n@@ -228,6 +229,20 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (strcmp(var, \"receive.procreceiverefs\") == 0) {\n+\t\tchar *prefix;\n+\t\tint len;\n+\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tprefix = xstrdup(value);\n+\t\tlen = strlen(prefix);\n+\t\twhile (len && prefix[len - 1] == '/')\n+\t\t\tprefix[--len] = '\\0';\n+\t\tstring_list_insert(&proc_receive_refs, prefix);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -1720,15 +1735,26 @@ static void execute_commands(struct command *commands,\n \t/* Try to find commands that have special prefix in their reference names,\n \t * and mark them to run an external \"proc-receive\" hook later.\n \t */\n-\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n-\t\t\tcontinue;\n+\tif (proc_receive_refs.nr > 0) {\n+\t\tstruct strbuf refname_full = STRBUF_INIT;\n+\t\tsize_t prefix_len;\n+\n+\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n+\t\tprefix_len = refname_full.len;\n \n-\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n-\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n-\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n-\t\t\trun_proc_receive = 1;\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tif (!should_process_cmd(cmd))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_setlen(&refname_full, prefix_len);\n+\t\t\tstrbuf_addstr(&refname_full, cmd->ref_name);\n+\t\t\tif (ref_is_matched(&proc_receive_refs, cmd->ref_name, refname_full.buf)) {\n+\t\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\t\trun_proc_receive = 1;\n+\t\t\t}\n \t\t}\n+\n+\t\tstrbuf_release(&refname_full);\n \t}\n \n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n@@ -2188,6 +2214,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n+\tstring_list_init(&proc_receive_refs, 0);\n+\n \tpacket_trace_identity(\"receive-pack\");\n \n \targc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);\n@@ -2303,5 +2331,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \toid_array_clear(&shallow);\n \toid_array_clear(&ref);\n \tfree((void *)push_cert_nonce);\n+\tstring_list_clear(&proc_receive_refs, 0);\n \treturn 0;\n }\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex a17fd9814c..6a4eaf2d8a 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -185,6 +185,14 @@ test_expect_success \"cleanup\" '\n \t)\n '\n \n+test_expect_success \"add two receive.procReceiveRefs settings\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit config --add receive.procReceiveRefs refs/for/ &&\n+\t\tgit config --add receive.procReceiveRefs refs/review/\n+\t)\n+'\n+\n # Refs of upstream : master(A)\n # Refs of workbench: master(A)  tags/v123\n # git push         :                       next(A)  refs/for/master/topic(A)\n@@ -739,4 +747,126 @@ test_expect_success \"push with options\" '\n \ttest_cmp expect actual\n '\n \n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/review/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"report update of all special refs\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/review/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/review/a/b/c/topic\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"report mixed refs update\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> bar\n+\t * [new branch] HEAD -> baz\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new branch] HEAD -> foo\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394601","messageId":"20200402163520.1338-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v6 2/7] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-02T16:35:15Z","receivedAt":"2020-04-02T16:35:38Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nGit calls an internal `execute_commands` function to handle commands\nsent from client to `git-receive-pack`.  Regardless of what references\nthe user pushes, git creates or updates the corresponding references if\nthe user has write-permission.  A contributor who has no\nwrite-permission, cannot push to the repository directly.  So, the\ncontributor has to write commits to an alternate location, and sends\npull request by emails or by other ways.  We call this workflow as a\ndistributed workflow.\n\nIt would be more convenient to work in a centralized workflow like what\nGerrit provided for some cases.  For example, a read-only user who\ncannot push to a branch directly can run the following `git push`\ncommand to push commits to a pseudo reference (has a prefix \"refs/for/\",\nnot \"refs/heads/\") to create a code review.\n\n    git push origin \\\n        HEAD:refs/for/<branch-name>/<session>\n\nThe `<branch-name>` in the above example can be as simple as \"master\",\nor a more complicated branch name like \"foo/bar\".  The `<session>` in\nthe above example command can be the local branch name of the client\nside, such as \"my/topic\".\n\nWe cannot implement a centralized workflow elegantly by using\n\"pre-receive\" + \"post-receive\", because Git will call the internal\nfunction \"execute_commands\" to create references (even the special\npseudo reference) between these two hooks.  Even though we can delete\nthe temporarily created pseudo reference via the \"post-receive\" hook,\nhaving a temporary reference is not safe for concurrent pushes.\n\nSo, add a filter and a new handler to support this kind of workflow.\nThe filter will check the prefix of the reference name, and if the\ncommand has a special reference name, the filter will turn a specific\nfield (`run_proc_receive`) on for the command.  Commands with this filed\nturned on will be executed by a new handler (an hook named\n\"proc-receive\") instead of the internal `execute_commands` function.\nWe can use this \"proc-receive\" command to create pull requests or send\nemails for code review.\n\nSuggested by Junio, this \"proc-receive\" hook reads the commands,\npush-options (optional), and send result using a protocol in pkt-line\nformat.  In the following example, The letter \"S\" stands for\n\"receive-pack\" and letter \"H\" stands for the hook.\n\n    # Version and capabilities negotiation.\n    S: PKT-LINE(version=1\\0push-options atomic...)\n    S: flush-pkt\n    H: PKT-LINE(version=1\\0push-options...)\n    H: flush-pkt\n\n    # Send commands from server to the hook.\n    S: PKT-LINE(old-oid new-oid ref)\n    S: ... ...\n    S: flush-pkt\n    # Only if push-options have been negotiated.\n    S: PKT-LINE(push-option)\n    S: ... ...\n    S: flush-pkt\n\n    # Receive result from the hook.\n    # OK, run this command successfully.\n    H: PKT-LINE(old-oid new-oid ref ok)\n    # NO, I reject it.\n    H: PKT-LINE(old-oid new-oid ref ng reason)\n    # OK, but use an alternate reference. (in latter commit)\n    H: PKT-LINE(old-oid new-oid ref ok ref:alt-ref)\n    # It will fallthrough to receive-pack to execute. (in latter commit)\n    H: PKT-LINE(old-oid new-oid ref ft)\n    H: ... ...\n    H: flush-pkt\n\nAfter receiving a command, the hook can create/update another alternate\nreference.  For example, a command for a reference \"refs/for/master\" may\ncreate a special reference, such as \"refs/pull/123/head\".  The alternate\nreference can be returned from the result in an extensible format like\n\"<old-oid> <new-oid> <reference> <status> [<message>]\".\n\nThe result will be stored in a command list, and \"receive-pack\" will use\nthe result to replace the commands that have specific `run_proc_receive`\nfield turned on.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Makefile                     |   1 +\n builtin/receive-pack.c       | 252 ++++++++++++++-\n t/helper/test-proc-receive.c | 172 ++++++++++\n t/helper/test-tool.c         |   1 +\n t/helper/test-tool.h         |   1 +\n t/t5411-proc-receive-hook.sh | 611 ++++++++++++++++++++++++++++++++++-\n 6 files changed, 1024 insertions(+), 14 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n\ndiff --git a/Makefile b/Makefile\nindex ef1ff2228f..1c52c280ce 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -725,6 +725,7 @@ TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\n+TEST_BUILTINS_OBJS += test-proc-receive.o\n TEST_BUILTINS_OBJS += test-progress.o\n TEST_BUILTINS_OBJS += test-reach.o\n TEST_BUILTINS_OBJS += test-read-cache.o\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 2cc18bbffd..45f2d426fc 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -308,11 +308,14 @@ static void write_head_info(void)\n \tpacket_flush(1);\n }\n \n+#define RUN_PROC_RECEIVE_SCHEDULED\t1\n+#define RUN_PROC_RECEIVE_RETURNED\t2\n struct command {\n \tstruct command *next;\n \tconst char *error_string;\n \tunsigned int skip_update:1,\n-\t\t     did_not_exist:1;\n+\t\t     did_not_exist:1,\n+\t\t     run_proc_receive:2;\n \tint index;\n \tstruct object_id old_oid;\n \tstruct object_id new_oid;\n@@ -817,6 +820,222 @@ static int run_update_hook(struct command *cmd)\n \treturn finish_command(&proc);\n }\n \n+static struct command *find_command_by_refname(const struct command *list,\n+\t\t\t\t\t       const char *refname)\n+{\n+\tfor ( ; list; list = list->next)\n+\t\tif (!strcmp(list->ref_name, refname))\n+\t\t\treturn (struct command *)list;\n+\treturn NULL;\n+}\n+\n+static int read_proc_receive_result(struct packet_reader *reader,\n+\t\t\t\t    struct command *commands)\n+{\n+\tstruct command *hint;\n+\tstruct command *cmd;\n+\tint code = 0;\n+\n+\thint = NULL;\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\t\tchar *status;\n+\t\tchar *msg = NULL;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ') {\n+\t\t\trp_error(\"protocol error: proc-receive expected 'old new ref status [msg]', got '%s'\",\n+\t\t\t\t reader->line);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\trefname = p;\n+\t\tstatus = strchr(p, ' ');\n+\t\tif (!status) {\n+\t\t\trp_error(\"protocol error: proc-receive expected 'old new ref status [msg]', got '%s'\",\n+\t\t\t\t reader->line);\n+\t\t\treturn -1;\n+\t\t}\n+\t\t*status++ = '\\0';\n+\t\tif (strlen(status) > 2 && *(status + 2) == ' ') {\n+\t\t\tmsg = status + 2;\n+\t\t\t*msg++ = '\\0';\n+\t\t}\n+\t\tif (strlen(status) != 2) {\n+\t\t\trp_error(\"protocol error: proc-receive has bad status '%s' for '%s'\",\n+\t\t\t\t status, reader->line);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\t/* first try searching at our hint, falling back to all refs */\n+\t\tif (hint)\n+\t\t\thint = find_command_by_refname(hint, refname);\n+\t\tif (!hint)\n+\t\t\thint = find_command_by_refname(commands, refname);\n+\t\tif (!hint) {\n+\t\t\twarning(\"proc-receive reported status on unknown ref: %s\",\n+\t\t\t\trefname);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!hint->run_proc_receive) {\n+\t\t\twarning(\"proc-receive reported status on ref of builtin command: %s\",\n+\t\t\t\trefname);\n+\t\t\tcontinue;\n+\t\t}\n+\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n+\t\toidcpy(&hint->old_oid, &old_oid);\n+\t\toidcpy(&hint->new_oid, &new_oid);\n+\t\tif (!strcmp(status, \"ng\")) {\n+\t\t\tif (msg)\n+\t\t\t\thint->error_string = xstrdup(msg);\n+\t\t\telse\n+\t\t\t\thint->error_string = \"failed\";\n+\t\t\tcode = 1;\n+\t\t} else if (strcmp(\"ok\", status)) {\n+\t\t\trp_error(\"protocol error: proc-receive has bad status '%s' for '%s'\",\n+\t\t\t\t status, reader->line);\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+\n+\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\tif (cmd->run_proc_receive &&\n+\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED))\n+\t\t    cmd->error_string = \"no report from proc-receive\";\n+\n+\treturn code;\n+}\n+\n+static int run_proc_receive_hook(struct command *commands,\n+\t\t\t\t const struct string_list *push_options)\n+{\n+\tstruct child_process proc = CHILD_PROCESS_INIT;\n+\tstruct async muxer;\n+\tstruct command *cmd;\n+\tconst char *argv[2];\n+\tstruct packet_reader reader;\n+\tstruct strbuf cap = STRBUF_INIT;\n+\tint pr_use_push_options = 0;\n+\tint version = 0;\n+\tint code;\n+\n+\targv[0] = find_hook(\"proc-receive\");\n+\tif (!argv[0]) {\n+\t\trp_error(\"cannot find hook 'proc-receive'\");\n+\t\treturn -1;\n+\t}\n+\targv[1] = NULL;\n+\n+\tproc.argv = argv;\n+\tproc.in = -1;\n+\tproc.out = -1;\n+\tproc.trace2_hook_name = \"proc-receive\";\n+\n+\tif (use_sideband) {\n+\t\tmemset(&muxer, 0, sizeof(muxer));\n+\t\tmuxer.proc = copy_to_sideband;\n+\t\tmuxer.in = -1;\n+\t\tcode = start_async(&muxer);\n+\t\tif (code)\n+\t\t\treturn code;\n+\t\tproc.err = muxer.in;\n+\t} else {\n+\t\tproc.err = 0;\n+\t}\n+\n+\tcode = start_command(&proc);\n+\tif (code) {\n+\t\tif (use_sideband)\n+\t\t\tfinish_async(&muxer);\n+\t\treturn code;\n+\t}\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\n+\t/* Version negotiaton */\n+\tpacket_reader_init(&reader, proc.out, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\tif (use_atomic)\n+\t\tstrbuf_addstr(&cap, \" atomic\");\n+\tif (use_push_options)\n+\t\tstrbuf_addstr(&cap, \" push-options\");\n+\tif (cap.len) {\n+\t\tpacket_write_fmt(proc.in, \"version=1%c%s\\n\", '\\0', cap.buf + 1);\n+\t\tstrbuf_release(&cap);\n+\t} else {\n+\t\tpacket_write_fmt(proc.in, \"version=1\\n\");\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader.pktlen > 8 && starts_with(reader.line, \"version=\")) {\n+\t\t\tversion = atoi(reader.line + 8);\n+\t\t\tlinelen = strlen(reader.line);\n+\t\t\tif (linelen < reader.pktlen) {\n+\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tpr_use_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (version != 1) {\n+\t\trp_error(\"protocol error: unknown proc-receive version '%d'\",\n+\t\t\t version);\n+\t\tcode = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\t/* Send commands */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tchar *old_hex, *new_hex;\n+\n+\t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)\n+\t\t\tcontinue;\n+\t\told_hex = oid_to_hex(&cmd->old_oid);\n+\t\tnew_hex = oid_to_hex(&cmd->new_oid);\n+\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n+\t\t\t\t old_hex, new_hex, cmd->ref_name);\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\t/* Send push options */\n+\tif (pr_use_push_options) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tfor_each_string_list_item(item, push_options)\n+\t\t\tpacket_write_fmt(proc.in, \"%s\", item->string);\n+\t\tpacket_flush(proc.in);\n+\t}\n+\n+\t/* Read result from proc-receive */\n+\tcode = read_proc_receive_result(&reader, commands);\n+\n+cleanup:\n+\tclose(proc.in);\n+\tclose(proc.out);\n+\tif (use_sideband)\n+\t\tfinish_async(&muxer);\n+\tif (finish_command(&proc)) {\n+\t\trp_error(\"proc-receive did not exit properly\");\n+\t\tcode = -1;\n+\t}\n+\tsigchain_pop(SIGPIPE);\n+\treturn code;\n+}\n+\n static char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n@@ -1392,7 +1611,7 @@ static void execute_commands_non_atomic(struct command *commands,\n \tstruct strbuf err = STRBUF_INIT;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\ttransaction = ref_transaction_begin(&err);\n@@ -1432,7 +1651,7 @@ static void execute_commands_atomic(struct command *commands,\n \t}\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\tcmd->error_string = update(cmd, si);\n@@ -1468,6 +1687,7 @@ static void execute_commands(struct command *commands,\n \tstruct iterate_data data;\n \tstruct async muxer;\n \tint err_fd = 0;\n+\tint run_proc_receive = 0;\n \n \tif (unpacker_error) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n@@ -1497,6 +1717,20 @@ static void execute_commands(struct command *commands,\n \n \treject_updates_to_hidden(commands);\n \n+\t/* Try to find commands that have special prefix in their reference names,\n+\t * and mark them to run an external \"proc-receive\" hook later.\n+\t */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!should_process_cmd(cmd))\n+\t\t\tcontinue;\n+\n+\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n+\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n+\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\trun_proc_receive = 1;\n+\t\t}\n+\t}\n+\n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n \t\t\tif (!cmd->error_string)\n@@ -1523,6 +1757,18 @@ static void execute_commands(struct command *commands,\n \tfree(head_name_to_free);\n \thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, NULL, NULL);\n \n+\tif (run_proc_receive) {\n+\t\tint code;\n+\n+\t\tcode = run_proc_receive_hook(commands, push_options);\n+\t\tif (code) {\n+\t\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\t\tif (!cmd->error_string  && (cmd->run_proc_receive || use_atomic))\n+\t\t\t\t\tcmd->error_string = \"fail to run proc-receive hook\";\n+\t\t\t}\n+\t\t}\n+\t}\n+\n \tif (use_atomic)\n \t\texecute_commands_atomic(commands, si);\n \telse\ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nnew file mode 100644\nindex 0000000000..ed30817905\n--- /dev/null\n+++ b/t/helper/test-proc-receive.c\n@@ -0,0 +1,172 @@\n+#include \"cache.h\"\n+#include \"connect.h\"\n+#include \"parse-options.h\"\n+#include \"pkt-line.h\"\n+#include \"string-list.h\"\n+#include \"test-tool.h\"\n+\n+static const char *proc_receive_usage[] = {\n+\t\"test-tool proc-receive [<options>...]\",\n+\tNULL\n+};\n+\n+static int version = 1;\n+static int verbose = 0;\n+static int no_push_options = 0;\n+static int use_atomic = 0;\n+static int use_push_options = 0;\n+static struct string_list returns = STRING_LIST_INIT_NODUP;\n+\n+struct command {\n+\tstruct command *next;\n+\tconst char *error_string;\n+\tunsigned int skip_update:1,\n+\t\t     did_not_exist:1;\n+\tint index;\n+\tstruct object_id old_oid;\n+\tstruct object_id new_oid;\n+\tchar ref_name[FLEX_ARRAY]; /* more */\n+};\n+\n+static void proc_receive_verison(struct packet_reader *reader) {\n+\tint server_version = 0;\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader->pktlen > 8 && starts_with(reader->line, \"version=\")) {\n+\t\t\tserver_version = atoi(reader->line+8);\n+\t\t\tlinelen = strlen(reader->line);\n+\t\t\tif (linelen < reader->pktlen) {\n+\t\t\t\tconst char *feature_list = reader->line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"atomic\"))\n+\t\t\t\t\tuse_atomic= 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tuse_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (server_version != 1)\n+\t\tdie(\"bad protocol version: %d\", server_version);\n+\n+\tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n+\t\t\t version, '\\0',\n+\t\t\t use_push_options && !no_push_options ? \"push-options\": \"\");\n+\tpacket_flush(1);\n+}\n+\n+static void proc_receive_read_commands(struct packet_reader *reader,\n+\t\t\t\t       struct command **commands)\n+{\n+\tstruct command **tail = commands;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tstruct command *cmd;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ')\n+\t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n+\t\t\t    reader->line);\n+\t\trefname = p;\n+\t\tFLEX_ALLOC_STR(cmd, ref_name, refname);\n+\t\toidcpy(&cmd->old_oid, &old_oid);\n+\t\toidcpy(&cmd->new_oid, &new_oid);\n+\n+\t\t*tail = cmd;\n+\t\ttail = &cmd->next;\n+\t}\n+}\n+\n+static void proc_receive_read_push_options(struct packet_reader *reader,\n+\t\t\t\t\t   struct string_list *options)\n+{\n+\n+\tif (no_push_options || !use_push_options)\n+\t       return;\n+\n+\twhile (1) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tstring_list_append(options, reader->line);\n+\t}\n+}\n+\n+int cmd__proc_receive(int argc, const char **argv)\n+{\n+\tstruct packet_reader reader;\n+\tstruct command *commands;\n+\tstruct string_list push_options = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"no-push-options\", &no_push_options,\n+\t\t\t \"disable push options\"),\n+\t\tOPT_STRING_LIST('r', \"return\", &returns, \"old/new/ref/status/msg\",\n+\t\t\t\t\"return of results\"),\n+\t\tOPT__VERBOSE(&verbose, \"be verbose\"),\n+\t\tOPT_INTEGER('V', \"version\", &version,\n+\t\t\t    \"use this protocol version number\"),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, \"test-tools\", options, proc_receive_usage, 0);\n+\tif (argc > 0)\n+\t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n+\n+\tpacket_reader_init(&reader, 0, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tproc_receive_verison(&reader);\n+\tproc_receive_read_commands(&reader, &commands);\n+\tproc_receive_read_push_options(&reader, &push_options);\n+\n+\tif (verbose) {\n+\t\tstruct command *cmd;\n+\n+\t\tif (use_push_options || use_atomic)\n+\t\t\tfprintf(stderr, \"proc-receive:%s%s\\n\",\n+\t\t\t\tuse_atomic? \" atomic\": \"\",\n+\t\t\t\tuse_push_options ? \" push_options\": \"\");\n+\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tchar *old_hex, *new_hex;\n+\n+\t\t\told_hex = oid_to_hex(&cmd->old_oid);\n+\t\t\tnew_hex = oid_to_hex(&cmd->new_oid);\n+\t\t\tfprintf(stderr, \"proc-receive< %s %s %s\\n\",\n+\t\t\t\told_hex, new_hex, cmd->ref_name);\n+\t\t}\n+\n+\t\tif (push_options.nr > 0) {\n+\t\t\tfor_each_string_list_item(item, &push_options)\n+\t\t\t\tfprintf(stderr, \"proc-receive< %s\\n\", item->string);\n+\t\t}\n+\n+\t\tif (returns.nr) {\n+\t\t\tfor_each_string_list_item(item, &returns)\n+\t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n+\t\t}\n+\t}\n+\n+\tif (returns.nr) {\n+\t\tfor_each_string_list_item(item, &returns)\n+\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n+\t}\n+\tpacket_flush(1);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 31eedcd241..f865433a61 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -44,6 +44,7 @@ static struct test_cmd cmds[] = {\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\n+\t{ \"proc-receive\", cmd__proc_receive},\n \t{ \"progress\", cmd__progress },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 4eb5e6609e..059008cf22 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -34,6 +34,7 @@ int cmd__parse_pathspec_file(int argc, const char** argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\n+int cmd__proc_receive(int argc, const char **argv);\n int cmd__progress(int argc, const char **argv);\n int cmd__reach(int argc, const char **argv);\n int cmd__read_cache(int argc, const char **argv);\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex 1784bcb584..a17fd9814c 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -43,8 +43,11 @@ create_commits_in () {\n # Format the output of git-push, git-show-ref and other commands to make a\n # user-friendly and stable text.  We can easily prepare the expect text\n # without having to worry about future changes of the commit ID and spaces\n-# of the output.  We also replce single quotes with double quotes, because\n-# it is boring to prepare unquoted single quotes in expect txt.\n+# of the output.  Single quotes are replaced with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect txt.  We also\n+# remove some locale error messages, which break test if we turn on\n+# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n+# on plumbing commands.\n make_user_friendly_and_stable_output () {\n \tsed \\\n \t\t-e \"s/  *\\$//\" \\\n@@ -54,13 +57,34 @@ make_user_friendly_and_stable_output () {\n \t\t-e \"s/$B/<COMMIT-B>/g\" \\\n \t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n \t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n-\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\"\n+\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\" \\\n+\t\t-e \"s#To ../upstream.git#To <URL/of/upstream.git>#\" \\\n+\t\t-e \"/^error: / d\"\n+}\n+\n+# Asynchronous sideband may generate inconsistent output messages,\n+# sort before comparison.\n+test_sorted_cmp () {\n+\tif ! $GIT_TEST_CMP \"$@\" >/dev/null 2>&1\n+\tthen\n+\t\tcmd=$GIT_TEST_CMP\n+\t\tfor f in \"$@\"\n+\t\tdo\n+\t\t\tsort \"$f\" >\"$f.sorted\"\n+\t\t\tcmd=\"$cmd \\\"$f.sorted\\\"\"\n+\t\tdone\n+\t\tif ! eval $cmd >/dev/null 2>&1\n+\t\tthen\n+\t\t\t$GIT_TEST_CMP \"$@\"\n+\t\tfi\n+\tfi\n }\n \n # Refs of upstream : master(B)  next(A)\n # Refs of workbench: master(A)           tags/v123\n test_expect_success \"setup\" '\n-\tgit init --bare upstream &&\n+\tupstream=upstream.git &&\n+\tgit init --bare \"$upstream\" &&\n \tgit init workbench &&\n \tcreate_commits_in workbench A B &&\n \t(\n@@ -68,7 +92,7 @@ test_expect_success \"setup\" '\n \t\t# Try to make a stable fixed width for abbreviated commit ID,\n \t\t# this fixed-width oid will be replaced with \"<OID>\".\n \t\tgit config core.abbrev 7 &&\n-\t\tgit remote add origin ../upstream &&\n+\t\tgit remote add origin ../$upstream &&\n \t\tgit update-ref refs/heads/master $A &&\n \t\tgit tag -m \"v123\" v123 $A &&\n \t\tgit push origin \\\n@@ -78,7 +102,7 @@ test_expect_success \"setup\" '\n \tTAG=$(git -C workbench rev-parse v123) &&\n \n \t# setup pre-receive hook\n-\tcat >upstream/hooks/pre-receive <<-\\EOF &&\n+\tcat >\"$upstream/hooks/pre-receive\" <<-\\EOF &&\n \t#!/bin/sh\n \n \techo >&2 \"# pre-receive hook\"\n@@ -90,7 +114,7 @@ test_expect_success \"setup\" '\n \tEOF\n \n \t# setup post-receive hook\n-\tcat >upstream/hooks/post-receive <<-\\EOF &&\n+\tcat >\"$upstream/hooks/post-receive\" <<-\\EOF &&\n \t#!/bin/sh\n \n \techo >&2 \"# post-receive hook\"\n@@ -102,8 +126,8 @@ test_expect_success \"setup\" '\n \tEOF\n \n \tchmod a+x \\\n-\t\tupstream/hooks/pre-receive \\\n-\t\tupstream/hooks/post-receive\n+\t\t\"$upstream/hooks/pre-receive\" \\\n+\t\t\"$upstream/hooks/post-receive\"\n '\n \n # Refs of upstream : master(B)  next(A)\n@@ -131,7 +155,7 @@ test_expect_success \"normal git-push command\" '\n \tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n-\tTo ../upstream\n+\tTo <URL/of/upstream.git>\n \t + <OID>...<OID> HEAD -> master (forced update)\n \t - [deleted] next\n \t * [new tag] v123 -> v123\n@@ -139,7 +163,7 @@ test_expect_success \"normal git-push command\" '\n \t * [new branch] HEAD -> a/b/c\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C upstream show-ref >out &&\n+\tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n \t<COMMIT-A> refs/heads/a/b/c\n@@ -150,4 +174,569 @@ test_expect_success \"normal git-push command\" '\n \ttest_cmp expect actual\n '\n \n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"no proc-receive hook, fail to push special ref\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_sorted_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic:                       next(A)  refs/for/master/topic(A)\n+test_expect_failure \"no proc-receive hook, fail all for atomic push\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_sorted_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad version)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success C_LOCALE_OUTPUT \"proc-receive bad protocol: unknown version\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: error: protocol error: unknown proc-receive version \"2\"\n+\tremote: fatal: the remote end hung up unexpectedly\n+\tremote: error: proc-receive did not exit properly\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_sorted_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive bad protocol: no report\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad oid)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"bad-id new-id ref ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: bad oid\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> bad-id new-id ref ok\n+\tremote: error: protocol error: proc-receive expected \"old new ref status [msg]\", got \"bad-id new-id ref ok\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_sorted_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: no status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: protocol error: proc-receive expected \"old new ref status [msg]\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_sorted_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic xx msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: unknown status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic xx msg\n+\tremote: error: protocol error: proc-receive has bad status \"xx\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_sorted_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic bad status\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: bad status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic bad status\n+\tremote: error: protocol error: proc-receive has bad status \"bad status\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_sorted_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (no message)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (has message)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng error msg\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (report status on builtin command)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/heads/master ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: warning on report for builtin command\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/heads/master ok\n+\twarning: proc-receive reported status on ref of builtin command: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t <OID>..<OID> <COMMIT-B> -> master\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_sorted_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\n+\n+test_expect_success \"setup proc-receive hook (ok)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: no report from proc-receive\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\twarning: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (no report from proc-receive)\n+\tEOF\n+\ttest_sorted_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success C_LOCALE_OUTPUT \"not support push options\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tfatal: the receiving end does not support push options\n+\tfatal: the remote end hung up unexpectedly\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"push with options\" '\n+\tgit -C workbench push \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394599","messageId":"20200402163520.1338-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v6 0/7] New proc-receive hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-02T16:35:13Z","receivedAt":"2020-04-02T16:35:39Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\n## Changes since v5\n\n+ Fixed code review issues on v5.\n\n  * s/cannot to/cannot/\n\n  * s/FLEX_ALLOC_MEM/FLEX_ALLOC_STR/\n\n  * Add flag `C_LOCALE_OUTPUT` on test cases which may break\n    gettext-poison test.\n\n  * Note: `rp_errors()` in \"receive-pack\" is executed on the\n    server side, I think leave them unmarked for i18n is better.\n\n+ Add t5412 for \"proc-receive\" hook test on HTTP protocol.\n\n+ Some test cases of t5412 will hang because of \"receive-pack\" die\n  earlier.  Replace `die()` with `rp_error()`.\n\n\n## Range-diff v5...v6\n\n1:  a8bcc20439 = 1:  1647024dfe transport: not report a non-head push as a branch\n2:  14641ec57e ! 2:  ed8d2c0e82 receive-pack: add new proc-receive hook\n    @@ builtin/receive-pack.c: static void write_head_info(void)\n      \tpacket_flush(1);\n      }\n      \n    -+#define RUN_PROC_RECEIVE_SCHEDULE\t1\n    ++#define RUN_PROC_RECEIVE_SCHEDULED\t1\n     +#define RUN_PROC_RECEIVE_RETURNED\t2\n      struct command {\n      \tstruct command *next;\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n     +\t\t    *p++ != ' ' ||\n     +\t\t    parse_oid_hex(p, &new_oid, &p) ||\n    -+\t\t    *p++ != ' ')\n    -+\t\t\tdie(\"protocol error: proc-receive expected 'old new ref status [msg]', got '%s'\",\n    -+\t\t\t    reader->line);\n    ++\t\t    *p++ != ' ') {\n    ++\t\t\trp_error(\"protocol error: proc-receive expected 'old new ref status [msg]', got '%s'\",\n    ++\t\t\t\t reader->line);\n    ++\t\t\treturn -1;\n    ++\t\t}\n     +\n     +\t\trefname = p;\n     +\t\tstatus = strchr(p, ' ');\n    -+\t\tif (!status)\n    -+\t\t\tdie(\"protocol error: proc-receive expected 'old new ref status [msg]', got '%s'\",\n    -+\t\t\t    reader->line);\n    ++\t\tif (!status) {\n    ++\t\t\trp_error(\"protocol error: proc-receive expected 'old new ref status [msg]', got '%s'\",\n    ++\t\t\t\t reader->line);\n    ++\t\t\treturn -1;\n    ++\t\t}\n     +\t\t*status++ = '\\0';\n     +\t\tif (strlen(status) > 2 && *(status + 2) == ' ') {\n     +\t\t\tmsg = status + 2;\n     +\t\t\t*msg++ = '\\0';\n     +\t\t}\n    -+\t\tif (strlen(status) != 2)\n    -+\t\t\tdie(\"protocol error: proc-receive has bad status '%s' for '%s'\",\n    -+\t\t\t    status, reader->line);\n    ++\t\tif (strlen(status) != 2) {\n    ++\t\t\trp_error(\"protocol error: proc-receive has bad status '%s' for '%s'\",\n    ++\t\t\t\t status, reader->line);\n    ++\t\t\treturn -1;\n    ++\t\t}\n     +\n     +\t\t/* first try searching at our hint, falling back to all refs */\n     +\t\tif (hint)\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\t\t\thint->error_string = \"failed\";\n     +\t\t\tcode = 1;\n     +\t\t} else if (strcmp(\"ok\", status)) {\n    -+\t\t\tdie(\"protocol error: proc-receive has bad status '%s' for '%s'\",\n    -+\t\t\t    status, reader->line);\n    ++\t\t\trp_error(\"protocol error: proc-receive has bad status '%s' for '%s'\",\n    ++\t\t\t\t status, reader->line);\n    ++\t\t\treturn -1;\n     +\t\t}\n     +\t}\n     +\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\n     +\targv[0] = find_hook(\"proc-receive\");\n     +\tif (!argv[0]) {\n    -+\t\trp_error(\"cannot to find hook 'proc-receive'\");\n    -+\t\treturn 1;\n    ++\t\trp_error(\"cannot find hook 'proc-receive'\");\n    ++\t\treturn -1;\n     +\t}\n     +\targv[1] = NULL;\n     +\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\t}\n     +\t}\n     +\n    -+\tif (version != 1)\n    -+\t\tdie(\"protocol error: unknown proc-receive version '%d'\", version);\n    ++\tif (version != 1) {\n    ++\t\trp_error(\"protocol error: unknown proc-receive version '%d'\",\n    ++\t\t\t version);\n    ++\t\tcode = -1;\n    ++\t\tgoto cleanup;\n    ++\t}\n     +\n     +\t/* Send commands */\n     +\tfor (cmd = commands; cmd; cmd = cmd->next) {\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\n     +\t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)\n     +\t\t\tcontinue;\n    -+\n     +\t\told_hex = oid_to_hex(&cmd->old_oid);\n     +\t\tnew_hex = oid_to_hex(&cmd->new_oid);\n    -+\n     +\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n     +\t\t\t\t old_hex, new_hex, cmd->ref_name);\n     +\t}\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\n     +\t\tfor_each_string_list_item(item, push_options)\n     +\t\t\tpacket_write_fmt(proc.in, \"%s\", item->string);\n    -+\n     +\t\tpacket_flush(proc.in);\n     +\t}\n     +\n     +\t/* Read result from proc-receive */\n     +\tcode = read_proc_receive_result(&reader, commands);\n    ++\n    ++cleanup:\n     +\tclose(proc.in);\n     +\tclose(proc.out);\n     +\tif (use_sideband)\n     +\t\tfinish_async(&muxer);\n    -+\tif (finish_command(&proc))\n    -+\t\tdie(\"proc-receive did not exit properly\");\n    -+\n    ++\tif (finish_command(&proc)) {\n    ++\t\trp_error(\"proc-receive did not exit properly\");\n    ++\t\tcode = -1;\n    ++\t}\n     +\tsigchain_pop(SIGPIPE);\n    -+\n     +\treturn code;\n     +}\n     +\n    @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n     +\n     +\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n     +\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n    -+\t\t\tcmd->run_proc_receive = 1;\n    ++\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n     +\t\t\trun_proc_receive = 1;\n     +\t\t}\n     +\t}\n    @@ t/helper/test-proc-receive.c (new)\n     +\t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n     +\t\t\t    reader->line);\n     +\t\trefname = p;\n    -+\t\tFLEX_ALLOC_MEM(cmd, ref_name, refname, strlen(refname));\n    ++\t\tFLEX_ALLOC_STR(cmd, ref_name, refname);\n     +\t\toidcpy(&cmd->old_oid, &old_oid);\n     +\t\toidcpy(&cmd->new_oid, &new_oid);\n     +\n    @@ t/helper/test-tool.h: int cmd__parse_pathspec_file(int argc, const char** argv);\n      int cmd__read_cache(int argc, const char **argv);\n     \n      ## t/t5411-proc-receive-hook.sh ##\n    +@@ t/t5411-proc-receive-hook.sh: create_commits_in () {\n    + # Format the output of git-push, git-show-ref and other commands to make a\n    + # user-friendly and stable text.  We can easily prepare the expect text\n    + # without having to worry about future changes of the commit ID and spaces\n    +-# of the output.  We also replce single quotes with double quotes, because\n    +-# it is boring to prepare unquoted single quotes in expect txt.\n    ++# of the output.  Single quotes are replaced with double quotes, because\n    ++# it is boring to prepare unquoted single quotes in expect txt.  We also\n    ++# remove some locale error messages, which break test if we turn on\n    ++# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n    ++# on plumbing commands.\n    + make_user_friendly_and_stable_output () {\n    + \tsed \\\n    + \t\t-e \"s/  *\\$//\" \\\n     @@ t/t5411-proc-receive-hook.sh: make_user_friendly_and_stable_output () {\n    - \t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\"\n    - }\n    - \n    + \t\t-e \"s/$B/<COMMIT-B>/g\" \\\n    + \t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n    + \t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n    +-\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\"\n    ++\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\" \\\n    ++\t\t-e \"s#To ../upstream.git#To <URL/of/upstream.git>#\" \\\n    ++\t\t-e \"/^error: / d\"\n    ++}\n    ++\n     +# Asynchronous sideband may generate inconsistent output messages,\n     +# sort before comparison.\n     +test_sorted_cmp () {\n    @@ t/t5411-proc-receive-hook.sh: make_user_friendly_and_stable_output () {\n     +\t\t\t$GIT_TEST_CMP \"$@\"\n     +\t\tfi\n     +\tfi\n    -+}\n    -+\n    + }\n    + \n      # Refs of upstream : master(B)  next(A)\n      # Refs of workbench: master(A)           tags/v123\n      test_expect_success \"setup\" '\n    +-\tgit init --bare upstream &&\n    ++\tupstream=upstream.git &&\n    ++\tgit init --bare \"$upstream\" &&\n    + \tgit init workbench &&\n    + \tcreate_commits_in workbench A B &&\n    + \t(\n    +@@ t/t5411-proc-receive-hook.sh: test_expect_success \"setup\" '\n    + \t\t# Try to make a stable fixed width for abbreviated commit ID,\n    + \t\t# this fixed-width oid will be replaced with \"<OID>\".\n    + \t\tgit config core.abbrev 7 &&\n    +-\t\tgit remote add origin ../upstream &&\n    ++\t\tgit remote add origin ../$upstream &&\n    + \t\tgit update-ref refs/heads/master $A &&\n    + \t\tgit tag -m \"v123\" v123 $A &&\n    + \t\tgit push origin \\\n    +@@ t/t5411-proc-receive-hook.sh: test_expect_success \"setup\" '\n    + \tTAG=$(git -C workbench rev-parse v123) &&\n    + \n    + \t# setup pre-receive hook\n    +-\tcat >upstream/hooks/pre-receive <<-\\EOF &&\n    ++\tcat >\"$upstream/hooks/pre-receive\" <<-\\EOF &&\n    + \t#!/bin/sh\n    + \n    + \techo >&2 \"# pre-receive hook\"\n    +@@ t/t5411-proc-receive-hook.sh: test_expect_success \"setup\" '\n    + \tEOF\n    + \n    + \t# setup post-receive hook\n    +-\tcat >upstream/hooks/post-receive <<-\\EOF &&\n    ++\tcat >\"$upstream/hooks/post-receive\" <<-\\EOF &&\n    + \t#!/bin/sh\n    + \n    + \techo >&2 \"# post-receive hook\"\n    +@@ t/t5411-proc-receive-hook.sh: test_expect_success \"setup\" '\n    + \tEOF\n    + \n    + \tchmod a+x \\\n    +-\t\tupstream/hooks/pre-receive \\\n    +-\t\tupstream/hooks/post-receive\n    ++\t\t\"$upstream/hooks/pre-receive\" \\\n    ++\t\t\"$upstream/hooks/post-receive\"\n    + '\n    + \n    + # Refs of upstream : master(B)  next(A)\n    +@@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n    + \tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n    +-\tTo ../upstream\n    ++\tTo <URL/of/upstream.git>\n    + \t + <OID>...<OID> HEAD -> master (forced update)\n    + \t - [deleted] next\n    + \t * [new tag] v123 -> v123\n    +@@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n    + \t * [new branch] HEAD -> a/b/c\n    + \tEOF\n    + \ttest_cmp expect actual &&\n    +-\tgit -C upstream show-ref >out &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    + \tmake_user_friendly_and_stable_output <out >actual &&\n    + \tcat >expect <<-EOF &&\n    + \t<COMMIT-A> refs/heads/a/b/c\n     @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n      \ttest_cmp expect actual\n      '\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of workbench: master(A)  tags/v123\n     +test_expect_success \"cleanup\" '\n     +\t(\n    -+\t\tcd upstream &&\n    ++\t\tcd \"$upstream\" &&\n     +\t\tgit update-ref -d refs/review/master/topic &&\n     +\t\tgit update-ref -d refs/tags/v123 &&\n     +\t\tgit update-ref -d refs/heads/a/b/c\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: # pre-receive hook\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: error: cannot to find hook \"proc-receive\"\n    ++\tremote: error: cannot find hook \"proc-receive\"\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n    -+\tTo ../upstream\n    ++\tTo <URL/of/upstream.git>\n     +\t * [new branch] HEAD -> next\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n    -+\terror: failed to push some refs to \"../upstream\"\n     +\tEOF\n    -+\ttest_cmp expect actual &&\n    -+\tgit -C upstream show-ref >out &&\n    ++\ttest_sorted_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)             next(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +test_expect_success \"cleanup\" '\n    -+\tgit -C upstream update-ref -d refs/heads/next\n    ++\tgit -C \"$upstream\" update-ref -d refs/heads/next\n     +'\n     +\n     +# Refs of upstream : master(A)\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: # pre-receive hook\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: error: cannot to find hook \"proc-receive\"\n    -+\tTo ../upstream\n    ++\tremote: error: cannot find hook \"proc-receive\"\n    ++\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n    -+\terror: failed to push some refs to \"../upstream\"\n     +\tEOF\n    -+\ttest_cmp expect actual &&\n    -+\tgit -C upstream show-ref >out &&\n    ++\ttest_sorted_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook (bad version)\" '\n    -+\tcat >upstream/hooks/proc-receive <<-EOF &&\n    ++\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n     +\t#!/bin/sh\n     +\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\n     +\ttest-tool proc-receive -v --version 2\n     +\tEOF\n    -+\tchmod a+x upstream/hooks/proc-receive\n    ++\tchmod a+x \"$upstream/hooks/proc-receive\"\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive bad protocol: unknown version\" '\n    ++test_expect_success C_LOCALE_OUTPUT \"proc-receive bad protocol: unknown version\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    -+\tmake_user_friendly_and_stable_output <out | grep \"protocol error\" >actual &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    -+\tfatal: protocol error: unknown proc-receive version \"2\"\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: error: protocol error: unknown proc-receive version \"2\"\n    ++\tremote: fatal: the remote end hung up unexpectedly\n    ++\tremote: error: proc-receive did not exit properly\n    ++\tTo <URL/of/upstream.git>\n    ++\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\tEOF\n    -+\ttest_cmp expect actual &&\n    -+\tgit -C upstream show-ref >out &&\n    ++\ttest_sorted_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook (no report)\" '\n    -+\tcat >upstream/hooks/proc-receive <<-EOF\n    ++\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n     +\t#!/bin/sh\n     +\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n    -+\tTo ../upstream\n    ++\tTo <URL/of/upstream.git>\n     +\t * [new branch] HEAD -> next\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n    -+\terror: failed to push some refs to \"../upstream\"\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\tgit -C upstream show-ref >out &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)             next(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +test_expect_success \"cleanup\" '\n    -+\tgit -C upstream update-ref -d refs/heads/next\n    ++\tgit -C \"$upstream\" update-ref -d refs/heads/next\n     +\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook (bad oid)\" '\n    -+\tcat >upstream/hooks/proc-receive <<-EOF\n    ++\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n     +\t#!/bin/sh\n     +\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic\\\n     +\t\t>out 2>&1 &&\n    -+\tmake_user_friendly_and_stable_output <out | grep \"protocol error\" >actual &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    -+\tfatal: protocol error: proc-receive expected \"old new ref status [msg]\", got \"bad-id new-id ref ok\"\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> bad-id new-id ref ok\n    ++\tremote: error: protocol error: proc-receive expected \"old new ref status [msg]\", got \"bad-id new-id ref ok\"\n    ++\tTo <URL/of/upstream.git>\n    ++\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\tEOF\n    -+\ttest_cmp expect actual &&\n    -+\tgit -C upstream show-ref >out &&\n    ++\ttest_sorted_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook (no status)\" '\n    -+\tcat >upstream/hooks/proc-receive <<-EOF\n    ++\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n     +\t#!/bin/sh\n     +\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    -+\tmake_user_friendly_and_stable_output <out | grep \"protocol error\" >actual &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    -+\tfatal: protocol error: proc-receive expected \"old new ref status [msg]\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: error: protocol error: proc-receive expected \"old new ref status [msg]\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n    ++\tTo <URL/of/upstream.git>\n    ++\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\tEOF\n    -+\ttest_cmp expect actual &&\n    -+\tgit -C upstream show-ref >out &&\n    ++\ttest_sorted_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook (unknown status)\" '\n    -+\tcat >upstream/hooks/proc-receive <<-EOF\n    ++\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n     +\t#!/bin/sh\n     +\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\t\tHEAD:refs/for/master/topic \\\n     +\t\t\t>out 2>&1 &&\n    -+\tmake_user_friendly_and_stable_output <out | grep \"protocol error\" >actual &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    -+\tfatal: protocol error: proc-receive has bad status \"xx\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic xx msg\n    ++\tremote: error: protocol error: proc-receive has bad status \"xx\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n    ++\tTo <URL/of/upstream.git>\n    ++\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\tEOF\n    -+\ttest_cmp expect actual &&\n    -+\tgit -C upstream show-ref >out &&\n    ++\ttest_sorted_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook (bad status)\" '\n    -+\tcat >upstream/hooks/proc-receive <<-EOF\n    ++\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n     +\t#!/bin/sh\n     +\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    -+\tmake_user_friendly_and_stable_output <out | grep \"protocol error\" >actual &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    -+\tfatal: protocol error: proc-receive has bad status \"bad status\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic bad status\n    ++\tremote: error: protocol error: proc-receive has bad status \"bad status\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n    ++\tTo <URL/of/upstream.git>\n    ++\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\tEOF\n    -+\ttest_cmp expect actual &&\n    -+\tgit -C upstream show-ref >out &&\n    ++\ttest_sorted_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook (ng)\" '\n    -+\tcat >upstream/hooks/proc-receive <<-EOF\n    ++\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n     +\t#!/bin/sh\n     +\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng\n    -+\tTo ../upstream\n    ++\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n    -+\terror: failed to push some refs to \"../upstream\"\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\tgit -C upstream show-ref >out &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook (ng message)\" '\n    -+\tcat >upstream/hooks/proc-receive <<-EOF\n    ++\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n     +\t#!/bin/sh\n     +\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng error msg\n    -+\tTo ../upstream\n    ++\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n    -+\terror: failed to push some refs to \"../upstream\"\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\tgit -C upstream show-ref >out &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook (report status on builtin command)\" '\n    -+\tcat >upstream/hooks/proc-receive <<-EOF\n    ++\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n     +\t#!/bin/sh\n     +\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\twarning: proc-receive reported status on ref of builtin command: refs/heads/master\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n    -+\tTo ../upstream\n    ++\tTo <URL/of/upstream.git>\n     +\t <OID>..<OID> <COMMIT-B> -> master\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n    -+\terror: failed to push some refs to \"../upstream\"\n     +\tEOF\n     +\ttest_sorted_cmp expect actual &&\n    -+\tgit -C upstream show-ref >out &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-B> refs/heads/master\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +'\n     +\n     +test_expect_success \"cleanup\" '\n    -+\tgit -C upstream update-ref refs/heads/master $A\n    ++\tgit -C \"$upstream\" update-ref refs/heads/master $A\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook (ok)\" '\n    -+\tcat >upstream/hooks/proc-receive <<-EOF\n    ++\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n     +\t#!/bin/sh\n     +\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tTo ../upstream\n    ++\tTo <URL/of/upstream.git>\n     +\t * [new reference] HEAD -> refs/for/master/topic\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\tgit -C upstream show-ref >out &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n     +\twarning: proc-receive reported status on unknown ref: refs/for/master/topic\n    -+\tTo ../upstream\n    ++\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (no report from proc-receive)\n    -+\terror: failed to push some refs to \"../upstream\"\n     +\tEOF\n     +\ttest_sorted_cmp expect actual &&\n    -+\tgit -C upstream show-ref >out &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push -o ...  :                       refs/for/master/topic\n    -+test_expect_success \"not support push options\" '\n    ++test_expect_success C_LOCALE_OUTPUT \"not support push options\" '\n     +\ttest_must_fail git -C workbench push \\\n     +\t\t-o issue=123 \\\n     +\t\t-o reviewer=user1 \\\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tfatal: the remote end hung up unexpectedly\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\tgit -C upstream show-ref >out &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +'\n     +\n     +test_expect_success \"enable push options\" '\n    -+\tgit -C upstream config receive.advertisePushOptions true\n    ++\tgit -C \"$upstream\" config receive.advertisePushOptions true\n     +'\n     +\n     +# Refs of upstream : master(A)\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tTo ../upstream\n    ++\tTo <URL/of/upstream.git>\n     +\t * [new branch] HEAD -> next\n     +\t * [new reference] HEAD -> refs/for/master/topic\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\tgit -C upstream show-ref >out &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n3:  2440a474bd = 3:  1aa84fdf6f refs.c: refactor to reuse ref_is_hidden()\n4:  e041582643 ! 4:  a301edb86c receive-pack: new config receive.procReceiveRefs\n    @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n      \n     -\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n     -\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n    --\t\t\tcmd->run_proc_receive = 1;\n    +-\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n     -\t\t\trun_proc_receive = 1;\n     +\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n     +\t\t\tif (!should_process_cmd(cmd))\n    @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n     +\t\t\tstrbuf_setlen(&refname_full, prefix_len);\n     +\t\t\tstrbuf_addstr(&refname_full, cmd->ref_name);\n     +\t\t\tif (ref_is_matched(&proc_receive_refs, cmd->ref_name, refname_full.buf)) {\n    -+\t\t\t\tcmd->run_proc_receive = 1;\n    ++\t\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n     +\t\t\t\trun_proc_receive = 1;\n     +\t\t\t}\n      \t\t}\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"cleanup\" '\n      \n     +test_expect_success \"add two receive.procReceiveRefs settings\" '\n     +\t(\n    -+\t\tcd upstream &&\n    ++\t\tcd \"$upstream\" &&\n     +\t\tgit config --add receive.procReceiveRefs refs/for/ &&\n     +\t\tgit config --add receive.procReceiveRefs refs/review/\n     +\t)\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"push with options\" '\n     +# Refs of upstream : master(A)             next(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +test_expect_success \"cleanup\" '\n    -+\tgit -C upstream update-ref -d refs/heads/next\n    ++\tgit -C \"$upstream\" update-ref -d refs/heads/next\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook\" '\n    -+\tcat >upstream/hooks/proc-receive <<-EOF &&\n    ++\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n     +\t#!/bin/sh\n     +\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n     +\t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n    ++\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n     +\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n     +\tEOF\n    -+\tchmod a+x upstream/hooks/proc-receive\n    ++\tchmod a+x \"$upstream/hooks/proc-receive\"\n     +'\n     +\n     +# Refs of upstream : master(A)\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"push with options\" '\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n    ++\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tTo ../upstream\n    ++\tTo <URL/of/upstream.git>\n     +\t * [new reference] HEAD -> refs/for/next/topic\n     +\t * [new reference] HEAD -> refs/review/a/b/c/topic\n     +\t * [new reference] HEAD -> refs/for/master/topic\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\tgit -C upstream show-ref >out &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"push with options\" '\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook\" '\n    -+\tcat >upstream/hooks/proc-receive <<-EOF &&\n    ++\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n     +\t#!/bin/sh\n     +\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"push with options\" '\n     +\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n     +\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n     +\tEOF\n    -+\tchmod a+x upstream/hooks/proc-receive\n    ++\tchmod a+x \"$upstream/hooks/proc-receive\"\n     +'\n     +\n     +# Refs of upstream : master(A)\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"push with options\" '\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tTo ../upstream\n    ++\tTo <URL/of/upstream.git>\n     +\t * [new branch] HEAD -> bar\n     +\t * [new branch] HEAD -> baz\n     +\t * [new reference] HEAD -> refs/for/next/topic\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"push with options\" '\n     +\t * [new reference] HEAD -> refs/for/master/topic\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\tgit -C upstream show-ref >out &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/bar\n5:  439acbdb62 ! 5:  538fa71f48 receive-pack: refactor report for proc-receive\n    @@ builtin/receive-pack.c: static int read_proc_receive_result(struct packet_reader\n     +\t\t\t/* Reset \"run_proc_receive\" field, and continue to run in \"receive-pack\" */\n     +\t\t\thint->run_proc_receive = 0;\n     +\t\t} else {\n    - \t\t\tdie(\"protocol error: proc-receive has bad status '%s' for '%s'\",\n    - \t\t\t    status, reader->line);\n    - \t\t}\n    + \t\t\trp_error(\"protocol error: proc-receive has bad status '%s' for '%s'\",\n    + \t\t\t\t status, reader->line);\n    + \t\t\treturn -1;\n     @@ builtin/receive-pack.c: static void report(struct command *commands, const char *unpack_status)\n      \tpacket_buf_write(&buf, \"unpack %s\\n\",\n      \t\t\t unpack_status ? unpack_status : \"ok\");\n    @@ builtin/receive-pack.c: static void report(struct command *commands, const char\n     \n      ## t/t5411-proc-receive-hook.sh ##\n     @@ t/t5411-proc-receive-hook.sh: test_expect_success \"setup proc-receive hook\" '\n    - \tprintf >&2 \"# proc-receive hook\\n\"\n      \n      \ttest-tool proc-receive -v \\\n    --\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n    -+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok ref:refs/pull/123/head\" \\\n      \t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n    +-\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n     -\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n    ++\t\t-r \"$ZERO_OID $A refs/for/next/topic ok ref:refs/pull/123/head\" \\\n     +\t\t-r \"$ZERO_OID $A refs/for/master/topic ok ref:refs/pull/124/head\"\n      \tEOF\n    - \tchmod a+x upstream/hooks/proc-receive\n    + \tchmod a+x \"$upstream/hooks/proc-receive\"\n      '\n     @@ t/t5411-proc-receive-hook.sh: test_expect_success \"report update of all special refs\" '\n    - \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n      \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n      \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    --\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok ref:refs/pull/123/head\n      \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n    +-\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n     -\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n    ++\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok ref:refs/pull/123/head\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok ref:refs/pull/124/head\n      \tremote: # post-receive hook\n      \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n      \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n      \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    - \tTo ../upstream\n    + \tTo <URL/of/upstream.git>\n     -\t * [new reference] HEAD -> refs/for/next/topic\n     +\t * [new reference] HEAD -> refs/pull/123/head\n      \t * [new reference] HEAD -> refs/review/a/b/c/topic\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"report update of all special\n     +\t * [new reference] HEAD -> refs/pull/124/head\n      \tEOF\n      \ttest_cmp expect actual &&\n    - \tgit -C upstream show-ref >out &&\n    + \tgit -C \"$upstream\" show-ref >out &&\n     @@ t/t5411-proc-receive-hook.sh: test_expect_success \"setup proc-receive hook\" '\n      # git push         :                       bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n      test_expect_success \"report mixed refs update\" '\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"report mixed refs update\" '\n      \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n      \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n      \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    - \tTo ../upstream\n    + \tTo <URL/of/upstream.git>\n     +\t <OID>..<OID> <COMMIT-B> -> master\n      \t * [new branch] HEAD -> bar\n      \t * [new branch] HEAD -> baz\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"report mixed refs update\" '\n     +'\n     +\n     +test_expect_success \"config receive.procReceiveRefs for all ref/\" '\n    -+\tgit -C upstream config --add receive.procReceiveRefs refs/\n    ++\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs/\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook\" '\n    -+\tcat >upstream/hooks/proc-receive <<-EOF &&\n    ++\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n     +\t#!/bin/sh\n     +\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"report mixed refs update\" '\n     +\t\t-r \"$A $B refs/for/master/topic ok ref:refs/pull/123/head\" \\\n     +\t\t-r \"$B $A refs/for/next/topic ok ref:refs/pull/124/head\"\n     +\tEOF\n    -+\tchmod a+x upstream/hooks/proc-receive\n    ++\tchmod a+x \"$upstream/hooks/proc-receive\"\n     +'\n     +\n     +# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"report mixed refs update\" '\n     +\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n     +\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n     +\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/next/topic\n    -+\tTo ../upstream\n    ++\tTo <URL/of/upstream.git>\n     +\t <OID>..<OID> <COMMIT-B> -> bar\n     +\t - [deleted] foo\n     +\t + <OID>...<OID> HEAD -> master (forced update)\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"report mixed refs update\" '\n     +\t * [new reference] HEAD -> refs/pull/124/head\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    -+\tgit -C upstream show-ref >out &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-B> refs/heads/bar\n-:  ---------- > 6:  20289547f3 t5412: test proc-receive hook on HTTP protocol\n6:  24e5cddee0 = 7:  da9106a42d doc: add documentation for the proc-receive hook\n\n\nJiang Xin (7):\n  transport: not report a non-head push as a branch\n  receive-pack: add new proc-receive hook\n  refs.c: refactor to reuse ref_is_hidden()\n  receive-pack: new config receive.procReceiveRefs\n  receive-pack: refactor report for proc-receive\n  t5412: test proc-receive hook on HTTP protocol\n  doc: add documentation for the proc-receive hook\n\n Documentation/config/receive.txt           |  14 +\n Documentation/githooks.txt                 |  70 ++\n Makefile                                   |   1 +\n builtin/receive-pack.c                     | 300 ++++++-\n refs.c                                     |  11 +-\n refs.h                                     |   1 +\n t/helper/test-proc-receive.c               | 172 ++++\n t/helper/test-tool.c                       |   1 +\n t/helper/test-tool.h                       |   1 +\n t/t5411-proc-receive-hook.sh               | 950 ++++++++++++++++++++\n t/t5412-proc-receive-hook-http-protocol.sh | 960 +++++++++++++++++++++\n t/t5516-fetch-push.sh                      |   2 +-\n transport-helper.c                         |  64 +-\n transport.c                                |  62 +-\n 14 files changed, 2543 insertions(+), 66 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100755 t/t5412-proc-receive-hook-http-protocol.sh\n\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394598","messageId":"20200402163520.1338-6-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v6 5/7] receive-pack: refactor report for proc-receive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-02T16:35:18Z","receivedAt":"2020-04-02T16:35:40Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThe \"proc-receive\" may update one or more references, and will send its\nresult one by one in pkt-line format.  Each line of the result has four\nfields and one optional message field, as \"<old-oid> <new-oid> <ref>\n<status> [<message>]\".  See the following example:\n\n    # OK, run this command successfully.\n    PKT-LINE(old-oid new-oid ref ok)\n\n    # NO, I reject it.\n    PKT-LINE(old-oid new-oid ref ng reason)\n\n    # OK, but use an alternate reference.\n    PKT-LINE(old-oid new-oid ref ok ref:alt-ref)\n\n    # It will fallthrough to receive-pack to execute.\n    PKT-LINE(old-oid new-oid ref ft)\n\nThe first three fields have the same foramt as a command.\n\nThe forth field has a two-letter status code.  Available status code:\n\n* ok: The command runs successfully.  If the optional message has a\n  prefix \"ref:\", the hook has created/updated an alternate reference\n  instead.\n\n* ng: Fail to run the command. Error message is in the optional message\n  field.\n\n* ft: Will fallthrough to receive-pack to execute.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c       | 21 +++++++--\n t/t5411-proc-receive-hook.sh | 90 +++++++++++++++++++++++++++++++++---\n transport-helper.c           | 64 ++++++++++++-------------\n transport.c                  | 59 +++++++++++++----------\n 4 files changed, 168 insertions(+), 66 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 3b2f949255..5c403993a9 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -328,6 +328,7 @@ static void write_head_info(void)\n struct command {\n \tstruct command *next;\n \tconst char *error_string;\n+\tconst char *extra_string;\n \tunsigned int skip_update:1,\n \t\t     did_not_exist:1,\n \t\t     run_proc_receive:2;\n@@ -912,7 +913,12 @@ static int read_proc_receive_result(struct packet_reader *reader,\n \t\t\telse\n \t\t\t\thint->error_string = \"failed\";\n \t\t\tcode = 1;\n-\t\t} else if (strcmp(\"ok\", status)) {\n+\t\t} else if (!strcmp(\"ok\", status)) {\n+\t\t\thint->extra_string = xstrdup_or_null(msg);\n+\t\t} else if (!strcmp(\"ft\", status)) {\n+\t\t\t/* Reset \"run_proc_receive\" field, and continue to run in \"receive-pack\" */\n+\t\t\thint->run_proc_receive = 0;\n+\t\t} else {\n \t\t\trp_error(\"protocol error: proc-receive has bad status '%s' for '%s'\",\n \t\t\t\t status, reader->line);\n \t\t\treturn -1;\n@@ -2171,12 +2177,17 @@ static void report(struct command *commands, const char *unpack_status)\n \tpacket_buf_write(&buf, \"unpack %s\\n\",\n \t\t\t unpack_status ? unpack_status : \"ok\");\n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!cmd->error_string)\n-\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n-\t\t\t\t\t cmd->ref_name);\n-\t\telse\n+\t\tif (!cmd->error_string) {\n+\t\t\tif (!cmd->extra_string)\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n+\t\t\t\t\t\t cmd->ref_name);\n+\t\t\telse\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s%c%s\\n\",\n+\t\t\t\t\t\t cmd->ref_name, ' ', cmd->extra_string);\n+\t\t} else {\n \t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n \t\t\t\t\t cmd->ref_name, cmd->error_string);\n+\t\t}\n \t}\n \tpacket_buf_flush(&buf);\n \ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex 6a4eaf2d8a..2b100d7edc 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -761,8 +761,8 @@ test_expect_success \"setup proc-receive hook\" '\n \n \ttest-tool proc-receive -v \\\n \t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n-\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n-\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok ref:refs/pull/123/head\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok ref:refs/pull/124/head\"\n \tEOF\n \tchmod a+x \"$upstream/hooks/proc-receive\"\n '\n@@ -787,16 +787,16 @@ test_expect_success \"report update of all special refs\" '\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n-\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n-\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok ref:refs/pull/123/head\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok ref:refs/pull/124/head\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \t * [new reference] HEAD -> refs/review/a/b/c/topic\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/pull/124/head\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -825,6 +825,7 @@ test_expect_success \"setup proc-receive hook\" '\n # git push         :                       bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n test_expect_success \"report mixed refs update\" '\n \tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n \t\tHEAD:refs/heads/bar \\\n \t\tHEAD:refs/heads/baz \\\n \t\tHEAD:refs/for/next/topic \\\n@@ -834,6 +835,7 @@ test_expect_success \"report mixed refs update\" '\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n@@ -845,12 +847,14 @@ test_expect_success \"report mixed refs update\" '\n \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n \tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n+\t <OID>..<OID> <COMMIT-B> -> master\n \t * [new branch] HEAD -> bar\n \t * [new branch] HEAD -> baz\n \t * [new reference] HEAD -> refs/for/next/topic\n@@ -864,6 +868,80 @@ test_expect_success \"report mixed refs update\" '\n \t<COMMIT-A> refs/heads/bar\n \t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"config receive.procReceiveRefs for all ref/\" '\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs/\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$B $A refs/heads/master ft\" \\\n+\t\t-r \"$A $ZERO_OID refs/heads/foo ft\" \\\n+\t\t-r \"$A $B refs/heads/bar ft\" \\\n+\t\t-r \"$A $B refs/for/master/topic ok ref:refs/pull/123/head\" \\\n+\t\t-r \"$B $A refs/for/next/topic ok ref:refs/pull/124/head\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      :                       (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"report test: fallthrough\" '\n+\tgit -C workbench push -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/heads/master ft\n+\tremote: proc-receive> <COMMIT-A> <ZERO-OID> refs/heads/foo ft\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/heads/bar ft\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/for/master/topic ok ref:refs/pull/123/head\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/next/topic ok ref:refs/pull/124/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/next/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID>..<OID> <COMMIT-B> -> bar\n+\t - [deleted] foo\n+\t + <OID>...<OID> HEAD -> master (forced update)\n+\t * [new reference] HEAD -> refs/pull/123/head\n+\t * [new reference] HEAD -> refs/pull/124/head\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/master\n \tEOF\n \ttest_cmp expect actual\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 20a7185ec4..cec3495d59 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -747,37 +747,39 @@ static int push_update_ref_status(struct strbuf *buf,\n \t\t\tmsg = xstrdup(msg);\n \t\tstrbuf_release(&msg_buf);\n \n-\t\tif (!strcmp(msg, \"no match\")) {\n-\t\t\tstatus = REF_STATUS_NONE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"up to date\")) {\n-\t\t\tstatus = REF_STATUS_UPTODATE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"non-fast forward\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_NONFASTFORWARD;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"already exists\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_ALREADY_EXISTS;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"fetch first\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_FETCH_FIRST;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"needs force\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_NEEDS_FORCE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"stale info\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_STALE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"forced update\")) {\n-\t\t\tforced = 1;\n-\t\t\tFREE_AND_NULL(msg);\n+\t\tif (status != REF_STATUS_OK) {\n+\t\t\tif (!strcmp(msg, \"no match\")) {\n+\t\t\t\tstatus = REF_STATUS_NONE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"up to date\")) {\n+\t\t\t\tstatus = REF_STATUS_UPTODATE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"non-fast forward\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_NONFASTFORWARD;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"already exists\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_ALREADY_EXISTS;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"fetch first\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_FETCH_FIRST;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"needs force\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_NEEDS_FORCE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"stale info\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_STALE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"forced update\")) {\n+\t\t\t\tforced = 1;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n \t\t}\n \t}\n \ndiff --git a/transport.c b/transport.c\nindex 272c0f4046..28731fa014 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -459,15 +459,18 @@ void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int v\n \t}\n }\n \n-static void print_ref_status(char flag, const char *summary,\n+static void print_ref_status(char flag, const char *summary, char *target_refname,\n \t\t\t     struct ref *to, struct ref *from, const char *msg,\n \t\t\t     int porcelain, int summary_width)\n {\n+\tif (!target_refname)\n+\t\ttarget_refname = to->name;\n+\n \tif (porcelain) {\n \t\tif (from)\n-\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, target_refname);\n \t\telse\n-\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, target_refname);\n \t\tif (msg)\n \t\t\tfprintf(stdout, \"%s (%s)\\n\", summary, msg);\n \t\telse\n@@ -481,9 +484,9 @@ static void print_ref_status(char flag, const char *summary,\n \t\tfprintf(stderr, \" %s%c %-*s%s \", red, flag, summary_width,\n \t\t\tsummary, reset);\n \t\tif (from)\n-\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(to->name));\n+\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(target_refname));\n \t\telse\n-\t\t\tfputs(prettify_refname(to->name), stderr);\n+\t\t\tfputs(prettify_refname(target_refname), stderr);\n \t\tif (msg) {\n \t\t\tfputs(\" (\", stderr);\n \t\t\tfputs(msg, stderr);\n@@ -495,18 +498,26 @@ static void print_ref_status(char flag, const char *summary,\n \n static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_width)\n {\n+\tchar *refname;\n+\n+\tif (ref->remote_status && !strncmp(ref->remote_status, \"ref:\", 4))\n+\t\trefname = ref->remote_status + 4;\n+\telse\n+\t\trefname = ref->name;\n+\n \tif (ref->deletion)\n-\t\tprint_ref_status('-', \"[deleted]\", ref, NULL, NULL,\n+\t\tprint_ref_status('-', \"[deleted]\", refname, ref, NULL, NULL,\n \t\t\t\t porcelain, summary_width);\n-\telse if (is_null_oid(&ref->old_oid))\n+\telse if (is_null_oid(&ref->old_oid)) {\n+\n \t\tprint_ref_status('*',\n-\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t (starts_with(refname, \"refs/tags/\")\n \t\t\t\t  ? \"[new tag]\"\n-\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t  : (starts_with(refname, \"refs/heads/\")\n \t\t\t\t     ? \"[new branch]\"\n \t\t\t\t     : \"[new reference]\")),\n-\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n-\telse {\n+\t\t\t\t refname, ref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t} else {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n \t\tconst char *msg;\n@@ -525,7 +536,7 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\tstrbuf_add_unique_abbrev(&quickref, &ref->new_oid,\n \t\t\t\t\t DEFAULT_ABBREV);\n \n-\t\tprint_ref_status(type, quickref.buf, ref, ref->peer_ref, msg,\n+\t\tprint_ref_status(type, quickref.buf, refname, ref, ref->peer_ref, msg,\n \t\t\t\t porcelain, summary_width);\n \t\tstrbuf_release(&quickref);\n \t}\n@@ -542,56 +553,56 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count,\n \n \tswitch(ref->status) {\n \tcase REF_STATUS_NONE:\n-\t\tprint_ref_status('X', \"[no match]\", ref, NULL, NULL,\n+\t\tprint_ref_status('X', \"[no match]\", NULL, ref, NULL, NULL,\n \t\t\t\t porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NODELETE:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, NULL,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, NULL,\n \t\t\t\t \"remote does not support deleting refs\",\n \t\t\t\t porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_UPTODATE:\n-\t\tprint_ref_status('=', \"[up to date]\", ref,\n+\t\tprint_ref_status('=', \"[up to date]\", NULL, ref,\n \t\t\t\t ref->peer_ref, NULL, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NONFASTFORWARD:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"non-fast-forward\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"already exists\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_FETCH_FIRST:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"fetch first\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NEEDS_FORCE:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"needs force\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_STALE:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"stale info\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_SHALLOW:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"new shallow roots not allowed\",\n \t\t\t\t porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REMOTE_REJECT:\n-\t\tprint_ref_status('!', \"[remote rejected]\", ref,\n+\t\tprint_ref_status('!', \"[remote rejected]\", NULL, ref,\n \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n \t\t\t\t ref->remote_status, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_EXPECTING_REPORT:\n-\t\tprint_ref_status('!', \"[remote failure]\", ref,\n+\t\tprint_ref_status('!', \"[remote failure]\", NULL, ref,\n \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n \t\t\t\t \"remote failed to report status\",\n \t\t\t\t porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_ATOMIC_PUSH_FAILED:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"atomic push failed\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_OK:\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394600","messageId":"20200402163520.1338-8-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v6 7/7] doc: add documentation for the proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-02T16:35:20Z","receivedAt":"2020-04-02T16:35:42Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd documentation for the new \"proc-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/githooks.txt | 70 ++++++++++++++++++++++++++++++++++++++\n 1 file changed, 70 insertions(+)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 3dccab5375..10ea5c1f18 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -333,6 +333,76 @@ The default 'update' hook, when enabled--and with\n `hooks.allowunannotated` config option unset or set to false--prevents\n unannotated tags to be pushed.\n \n+[[proc-receive]]\n+proc-receive\n+~~~~~~~~~~~~\n+This hook is invoked by linkgit:git-receive-pack[1] when it reacts to\n+special `git push` command.  According to refnames of the commands which\n+`git push` sends to 'git-receive-pack', the commands will be devided\n+into two groups by matching what the `receive.procReceiveRefs`\n+configuration variable defines.  One group of the commands will execute\n+the internal `execute_commands` function to update the corresponding\n+refnames, and the other group of commands which have matching refnames\n+will execute this 'proc-receive' hook to create pull requests, etc.\n+If there is no `receive.procReceiveRefs` settings, this hook won't\n+execute at all, and all commands are sent to the internal\n+`execute_commands` function.\n+\n+Its exit status only determines the success or failure of the group of\n+commands with special refnames, unless atomic push is in use.\n+\n+This hook executes once for the receive operation.  It takes no\n+arguments, but will talk a protocol in pkt-line format with the\n+'receive-pack' for reading commands, push-options (optional), and\n+sending result.  In the following example, The letter \"S\" stands for\n+\"receive-pack\" and letter \"H\" stands for the hook.\n+\n+    # Version and capabilities negotiation.\n+    S: PKT-LINE(version=1\\0push-options atomic...)\n+    S: flush-pkt\n+    H: PKT-LINE(version=1\\0push-options...)\n+    H: flush-pkt\n+\n+    # Send commands from server to the hook.\n+    S: PKT-LINE(old-oid new-oid ref)\n+    S: ... ...\n+    S: flush-pkt\n+    # Only if push-options have been negotiated.\n+    S: PKT-LINE(push-option)\n+    S: ... ...\n+    S: flush-pkt\n+\n+    # Receive result from the hook.\n+    # OK, run this command successfully.\n+    H: PKT-LINE(old-oid new-oid ref ok)\n+    # NO, I reject it.\n+    H: PKT-LINE(old-oid new-oid ref ng reason)\n+    # OK, but use an alternate reference.\n+    H: PKT-LINE(old-oid new-oid ref ok ref:alt-ref)\n+    # It will fallthrough to receive-pack to execute.\n+    H: PKT-LINE(old-oid new-oid ref ft)\n+    H: ... ...\n+    H: flush-pkt\n+\n+The \"proc-receive\" hook may update one or more references, and will send\n+its result one by one in pkt-line format.  Each line of the result has\n+four fields and one optional message field, like \"<old-oid> <new-oid>\n+<ref> <status> [<message>]\".\n+\n+The first three fields are the same as those of the commands for\n+\"receive-pack\".\n+\n+The forth field has a two-letter status code.  Available status codes:\n+\n+* ok: The command runs successfully.  If the optional message has a\n+  prefix \"ref:\", the hook has created/updated an alternate reference\n+  instead.\n+\n+* ng: Fail to run the command.  Error message is given in the optional\n+  message field.\n+\n+* ft: Will fallthrough to receive-pack to execute.\n+\n [[post-receive]]\n post-receive\n ~~~~~~~~~~~~\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394602","messageId":"20200402163520.1338-7-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200304113312.34229-1-zhiyou.jx@alibaba-inc.com","subject":"[PATCH v6 6/7] t5412: test proc-receive hook on HTTP protocol","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-02T16:35:19Z","receivedAt":"2020-04-02T16:35:43Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nCopy from t5411 to test \"proc-receive\" hook on HTTP protocol.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5412-proc-receive-hook-http-protocol.sh | 960 +++++++++++++++++++++\n 1 file changed, 960 insertions(+)\n create mode 100755 t/t5412-proc-receive-hook-http-protocol.sh\n\ndiff --git a/t/t5412-proc-receive-hook-http-protocol.sh b/t/t5412-proc-receive-hook-http-protocol.sh\nnew file mode 100755\nindex 0000000000..a3a7beca27\n--- /dev/null\n+++ b/t/t5412-proc-receive-hook-http-protocol.sh\n@@ -0,0 +1,960 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook for HTTP protocol'\n+\n+. ./test-lib.sh\n+\n+ROOT_PATH=\"$PWD\"\n+. \"$TEST_DIRECTORY\"/lib-gpg.sh\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+. \"$TEST_DIRECTORY\"/lib-terminal.sh\n+start_httpd\n+\n+# Create commits in <repo> and assign each commit's oid to shell variables\n+# given in the arguments (A, B, and C). E.g.:\n+#\n+#     create_commits_in <repo> A B C\n+#\n+# NOTE: Never calling this function from a subshell since variable\n+# assignments will disappear when subshell exits.\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{})\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  We can easily prepare the expect text\n+# without having to worry about future changes of the commit ID and spaces\n+# of the output.  Single quotes are replaced with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect txt.  We also\n+# remove some locale error messages, which break test if we turn on\n+# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n+# on plumbing commands.\n+make_user_friendly_and_stable_output () {\n+\tsed \\\n+\t\t-e \"s/  *\\$//\" \\\n+\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/'/\\\"/g\" \\\n+\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n+\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n+\t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n+\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n+\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\" \\\n+\t\t-e \"s#To .*/upstream.git#To <URL/of/upstream.git>#\" \\\n+\t\t-e \"/^error: / d\" \\\n+\t\t-e \"/^remote: fatal: /d\"\n+}\n+\n+# Asynchronous sideband may generate inconsistent output messages,\n+# sort before comparison.\n+test_sorted_cmp () {\n+\tif ! $GIT_TEST_CMP \"$@\" >/dev/null 2>&1\n+\tthen\n+\t\tcmd=$GIT_TEST_CMP\n+\t\tfor f in \"$@\"\n+\t\tdo\n+\t\t\tsort \"$f\" >\"$f.sorted\"\n+\t\t\tcmd=\"$cmd \\\"$f.sorted\\\"\"\n+\t\tdone\n+\t\tif ! eval $cmd >/dev/null 2>&1\n+\t\tthen\n+\t\t\t$GIT_TEST_CMP \"$@\"\n+\t\tfi\n+\tfi\n+}\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+test_expect_success \"setup\" '\n+\tgit init --bare upstream &&\n+\tgit -C upstream config http.receivepack true &&\n+\tgit init workbench &&\n+\tcreate_commits_in workbench A B &&\n+\t(\n+\t\tcd workbench &&\n+\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\tgit config core.abbrev 7 &&\n+\t\tgit remote add origin ../upstream &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit tag -m \"v123\" v123 $A &&\n+\t\tgit push origin \\\n+\t\t\t$B:refs/heads/master \\\n+\t\t\t$A:refs/heads/next\n+\t) &&\n+\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t# setup pre-receive hook\n+\tcat >upstream/hooks/pre-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# pre-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"pre-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\t# setup post-receive hook\n+\tcat >upstream/hooks/post-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# post-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"post-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\tchmod a+x \\\n+\t\tupstream/hooks/pre-receive \\\n+\t\tupstream/hooks/post-receive &&\n+\n+\tupstream=\"$HTTPD_DOCUMENT_ROOT_PATH/upstream.git\" &&\n+\tmv upstream \"$upstream\" &&\n+\tgit -C workbench remote set-url origin $HTTPD_URL/smart/upstream.git\n+'\n+\n+setup_askpass_helper\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"normal git-push command\" '\n+\tgit -C workbench push -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tHEAD:refs/heads/master \\\n+\t\tHEAD:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo <URL/of/upstream.git>\n+\t + <OID>...<OID> HEAD -> master (forced update)\n+\t - [deleted] next\n+\t * [new tag] v123 -> v123\n+\t * [new reference] HEAD -> refs/review/master/topic\n+\t * [new branch] HEAD -> a/b/c\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\n+\n+test_expect_success \"add two receive.procReceiveRefs settings\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit config --add receive.procReceiveRefs refs/for/ &&\n+\t\tgit config --add receive.procReceiveRefs refs/review/\n+\t)\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"no proc-receive hook, fail to push special ref\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_sorted_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic:                       next(A)  refs/for/master/topic(A)\n+test_expect_failure \"no proc-receive hook, fail all for atomic push\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_sorted_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad version)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive bad protocol: unknown version\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: error: protocol error: unknown proc-receive version \"2\"\n+\tremote: error: proc-receive did not exit properly\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_sorted_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive bad protocol: no report\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad oid)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"bad-id new-id ref ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: bad oid\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> bad-id new-id ref ok\n+\tremote: error: protocol error: proc-receive expected \"old new ref status [msg]\", got \"bad-id new-id ref ok\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_sorted_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: no status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: protocol error: proc-receive expected \"old new ref status [msg]\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_sorted_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic xx msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: unknown status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic xx msg\n+\tremote: error: protocol error: proc-receive has bad status \"xx\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_sorted_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic bad status\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: bad status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic bad status\n+\tremote: error: protocol error: proc-receive has bad status \"bad status\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_sorted_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (no message)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (has message)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng error msg\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (report status on builtin command)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/heads/master ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: warning on report for builtin command\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/heads/master ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t <OID>..<OID> <COMMIT-B> -> master\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_sorted_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\n+\n+test_expect_success \"setup proc-receive hook (ok)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: no report from proc-receive\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (no report from proc-receive)\n+\tEOF\n+\ttest_sorted_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success C_LOCALE_OUTPUT \"not support push options\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tfatal: the receiving end does not support push options\n+\tfatal: the remote end hung up unexpectedly\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"push with options\" '\n+\tgit -C workbench push \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok ref:refs/pull/123/head\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok ref:refs/pull/124/head\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/review/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"report update of all special refs\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/review/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok ref:refs/pull/123/head\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok ref:refs/pull/124/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/pull/123/head\n+\t * [new reference] HEAD -> refs/review/a/b/c/topic\n+\t * [new reference] HEAD -> refs/pull/124/head\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"report mixed refs update\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID>..<OID> <COMMIT-B> -> master\n+\t * [new branch] HEAD -> bar\n+\t * [new branch] HEAD -> baz\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new branch] HEAD -> foo\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"config receive.procReceiveRefs for all ref/\" '\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs/\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$B $A refs/heads/master ft\" \\\n+\t\t-r \"$A $ZERO_OID refs/heads/foo ft\" \\\n+\t\t-r \"$A $B refs/heads/bar ft\" \\\n+\t\t-r \"$A $B refs/for/master/topic ok ref:refs/pull/123/head\" \\\n+\t\t-r \"$B $A refs/for/next/topic ok ref:refs/pull/124/head\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      :                       (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"report test: fallthrough\" '\n+\tgit -C workbench push -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/heads/master ft\n+\tremote: proc-receive> <COMMIT-A> <ZERO-OID> refs/heads/foo ft\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/heads/bar ft\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/for/master/topic ok ref:refs/pull/123/head\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/next/topic ok ref:refs/pull/124/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/next/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID>..<OID> <COMMIT-B> -> bar\n+\t - [deleted] foo\n+\t + <OID>...<OID> HEAD -> master (forced update)\n+\t * [new reference] HEAD -> refs/pull/123/head\n+\t * [new reference] HEAD -> refs/pull/124/head\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394617","messageId":"xmqqftdl3fss.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200402163520.1338-1-worldhello.net@gmail.com","subject":"Re: [PATCH v6 0/7] New proc-receive hook for centralized workflow","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-04-02T18:26:11Z","receivedAt":"2020-04-02T18:26:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n>\n> ## Changes since v5\n>\n> + Fixed code review issues on v5.\n>\n>   * s/cannot to/cannot/\n>\n>   * s/FLEX_ALLOC_MEM/FLEX_ALLOC_STR/\n\nOK\n\n>   * Add flag `C_LOCALE_OUTPUT` on test cases which may break\n>     gettext-poison test.\n\nWe prefer to avoid the C_LOCALE_OUTPUT prerequiste when we can use\ntest_i18n<stuff> to make the poison test not care.  If it is too\nhard to arrange in these tests, it is OK to omit, though.\n\n>   * Note: `rp_errors()` in \"receive-pack\" is executed on the\n>     server side, I think leave them unmarked for i18n is better.\n\nHmph, I vaguely recall saying something similar and then got a\ndisagreement from somebody.  Perhaps Jonathan (cc'ed)?\n\nThanks.\n"},{"id":"394668","messageId":"20200403160838.6252-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqqftdl3fss.fsf@gitster.c.googlers.com","subject":"[PATCH v7 1/7] transport: not report a non-head push as a branch","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-03T16:08:32Z","receivedAt":"2020-04-03T16:08:48Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing a new reference (not a head or tag), report it as a new\nreference instead of a new branch.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411-proc-receive-hook.sh | 153 +++++++++++++++++++++++++++++++++++\n t/t5516-fetch-push.sh        |   2 +-\n transport.c                  |   9 ++-\n 3 files changed, 160 insertions(+), 4 deletions(-)\n create mode 100755 t/t5411-proc-receive-hook.sh\n\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nnew file mode 100755\nindex 0000000000..1784bcb584\n--- /dev/null\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -0,0 +1,153 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook'\n+\n+. ./test-lib.sh\n+\n+# Create commits in <repo> and assign each commit's oid to shell variables\n+# given in the arguments (A, B, and C). E.g.:\n+#\n+#     create_commits_in <repo> A B C\n+#\n+# NOTE: Never calling this function from a subshell since variable\n+# assignments will disappear when subshell exits.\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{})\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  We can easily prepare the expect text\n+# without having to worry about future changes of the commit ID and spaces\n+# of the output.  We also replce single quotes with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect txt.\n+make_user_friendly_and_stable_output () {\n+\tsed \\\n+\t\t-e \"s/  *\\$//\" \\\n+\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/'/\\\"/g\" \\\n+\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n+\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n+\t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n+\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n+\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\"\n+}\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+test_expect_success \"setup\" '\n+\tgit init --bare upstream &&\n+\tgit init workbench &&\n+\tcreate_commits_in workbench A B &&\n+\t(\n+\t\tcd workbench &&\n+\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\tgit config core.abbrev 7 &&\n+\t\tgit remote add origin ../upstream &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit tag -m \"v123\" v123 $A &&\n+\t\tgit push origin \\\n+\t\t\t$B:refs/heads/master \\\n+\t\t\t$A:refs/heads/next\n+\t) &&\n+\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t# setup pre-receive hook\n+\tcat >upstream/hooks/pre-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# pre-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"pre-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\t# setup post-receive hook\n+\tcat >upstream/hooks/post-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# post-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"post-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\tchmod a+x \\\n+\t\tupstream/hooks/pre-receive \\\n+\t\tupstream/hooks/post-receive\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"normal git-push command\" '\n+\tgit -C workbench push -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tHEAD:refs/heads/master \\\n+\t\tHEAD:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo ../upstream\n+\t + <OID>...<OID> HEAD -> master (forced update)\n+\t - [deleted] next\n+\t * [new tag] v123 -> v123\n+\t * [new reference] HEAD -> refs/review/master/topic\n+\t * [new branch] HEAD -> a/b/c\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 9ff041a093..9e4b9313b5 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1039,7 +1039,7 @@ test_force_fetch_tag \"annotated tag\" \"-f -a -m'tag message'\"\n test_expect_success 'push --porcelain' '\n \tmk_empty testrepo &&\n \techo >.git/foo  \"To testrepo\" &&\n-\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new branch]\"  &&\n+\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new reference]\"  &&\n \techo >>.git/foo \"Done\" &&\n \tgit push >.git/bar --porcelain  testrepo refs/heads/master:refs/remotes/origin/master &&\n \t(\ndiff --git a/transport.c b/transport.c\nindex 1fdc7dac1a..272c0f4046 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -500,9 +500,12 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\t\t\t porcelain, summary_width);\n \telse if (is_null_oid(&ref->old_oid))\n \t\tprint_ref_status('*',\n-\t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n-\t\t\t\"[new branch]\"),\n-\t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t  ? \"[new tag]\"\n+\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t     ? \"[new branch]\"\n+\t\t\t\t     : \"[new reference]\")),\n+\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394669","messageId":"20200403160838.6252-4-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqqftdl3fss.fsf@gitster.c.googlers.com","subject":"[PATCH v7 3/7] refs.c: refactor to reuse ref_is_hidden()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-03T16:08:34Z","receivedAt":"2020-04-03T16:08:51Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd new function `ref_is_matched()` to reuse `ref_is_hidden()`. Will use\nthis function for `receive-pack` to check commands with specific\nprefixes.\n\nTest case t5512 covered this change.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n refs.c | 11 ++++++++---\n refs.h |  1 +\n 2 files changed, 9 insertions(+), 3 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 1ab0bb54d3..229159ea1a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1389,13 +1389,18 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti\n }\n \n int ref_is_hidden(const char *refname, const char *refname_full)\n+{\n+\treturn ref_is_matched(hide_refs, refname, refname_full);\n+}\n+\n+int ref_is_matched(struct string_list *match_refs, const char *refname, const char *refname_full)\n {\n \tint i;\n \n-\tif (!hide_refs)\n+\tif (!match_refs)\n \t\treturn 0;\n-\tfor (i = hide_refs->nr - 1; i >= 0; i--) {\n-\t\tconst char *match = hide_refs->items[i].string;\n+\tfor (i = match_refs->nr - 1; i >= 0; i--) {\n+\t\tconst char *match = match_refs->items[i].string;\n \t\tconst char *subject;\n \t\tint neg = 0;\n \t\tconst char *p;\ndiff --git a/refs.h b/refs.h\nindex 545029c6d8..a2ea043f7f 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -739,6 +739,7 @@ int parse_hide_refs_config(const char *var, const char *value, const char *);\n  * parameter always points to the full ref name.\n  */\n int ref_is_hidden(const char *, const char *);\n+int ref_is_matched(struct string_list *, const char *, const char *);\n \n enum ref_type {\n \tREF_TYPE_PER_WORKTREE,\t  /* refs inside refs/ but not shared       */\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394671","messageId":"20200403160838.6252-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqqftdl3fss.fsf@gitster.c.googlers.com","subject":"[PATCH v7 2/7] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-03T16:08:33Z","receivedAt":"2020-04-03T16:08:52Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nGit calls an internal `execute_commands` function to handle commands\nsent from client to `git-receive-pack`.  Regardless of what references\nthe user pushes, git creates or updates the corresponding references if\nthe user has write-permission.  A contributor who has no\nwrite-permission, cannot push to the repository directly.  So, the\ncontributor has to write commits to an alternate location, and sends\npull request by emails or by other ways.  We call this workflow as a\ndistributed workflow.\n\nIt would be more convenient to work in a centralized workflow like what\nGerrit provided for some cases.  For example, a read-only user who\ncannot push to a branch directly can run the following `git push`\ncommand to push commits to a pseudo reference (has a prefix \"refs/for/\",\nnot \"refs/heads/\") to create a code review.\n\n    git push origin \\\n        HEAD:refs/for/<branch-name>/<session>\n\nThe `<branch-name>` in the above example can be as simple as \"master\",\nor a more complicated branch name like \"foo/bar\".  The `<session>` in\nthe above example command can be the local branch name of the client\nside, such as \"my/topic\".\n\nWe cannot implement a centralized workflow elegantly by using\n\"pre-receive\" + \"post-receive\", because Git will call the internal\nfunction \"execute_commands\" to create references (even the special\npseudo reference) between these two hooks.  Even though we can delete\nthe temporarily created pseudo reference via the \"post-receive\" hook,\nhaving a temporary reference is not safe for concurrent pushes.\n\nSo, add a filter and a new handler to support this kind of workflow.\nThe filter will check the prefix of the reference name, and if the\ncommand has a special reference name, the filter will turn a specific\nfield (`run_proc_receive`) on for the command.  Commands with this filed\nturned on will be executed by a new handler (an hook named\n\"proc-receive\") instead of the internal `execute_commands` function.\nWe can use this \"proc-receive\" command to create pull requests or send\nemails for code review.\n\nSuggested by Junio, this \"proc-receive\" hook reads the commands,\npush-options (optional), and send result using a protocol in pkt-line\nformat.  In the following example, The letter \"S\" stands for\n\"receive-pack\" and letter \"H\" stands for the hook.\n\n    # Version and capabilities negotiation.\n    S: PKT-LINE(version=1\\0push-options atomic...)\n    S: flush-pkt\n    H: PKT-LINE(version=1\\0push-options...)\n    H: flush-pkt\n\n    # Send commands from server to the hook.\n    S: PKT-LINE(old-oid new-oid ref)\n    S: ... ...\n    S: flush-pkt\n    # Only if push-options have been negotiated.\n    S: PKT-LINE(push-option)\n    S: ... ...\n    S: flush-pkt\n\n    # Receive result from the hook.\n    # OK, run this command successfully.\n    H: PKT-LINE(old-oid new-oid ref ok)\n    # NO, I reject it.\n    H: PKT-LINE(old-oid new-oid ref ng reason)\n    # OK, but use an alternate reference. (in latter commit)\n    H: PKT-LINE(old-oid new-oid ref ok ref:alt-ref)\n    # It will fallthrough to receive-pack to execute. (in latter commit)\n    H: PKT-LINE(old-oid new-oid ref ft)\n    H: ... ...\n    H: flush-pkt\n\nAfter receiving a command, the hook can create/update another alternate\nreference.  For example, a command for a reference \"refs/for/master\" may\ncreate a special reference, such as \"refs/pull/123/head\".  The alternate\nreference can be returned from the result in an extensible format like\n\"<old-oid> <new-oid> <reference> <status> [<message>]\".\n\nThe result will be stored in a command list, and \"receive-pack\" will use\nthe result to replace the commands that have specific `run_proc_receive`\nfield turned on.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Makefile                     |   1 +\n builtin/receive-pack.c       | 264 +++++++++++++++-\n t/helper/test-proc-receive.c | 172 ++++++++++\n t/helper/test-tool.c         |   1 +\n t/helper/test-tool.h         |   1 +\n t/t5411-proc-receive-hook.sh | 593 ++++++++++++++++++++++++++++++++++-\n 6 files changed, 1018 insertions(+), 14 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n\ndiff --git a/Makefile b/Makefile\nindex ef1ff2228f..1c52c280ce 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -725,6 +725,7 @@ TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\n+TEST_BUILTINS_OBJS += test-proc-receive.o\n TEST_BUILTINS_OBJS += test-progress.o\n TEST_BUILTINS_OBJS += test-reach.o\n TEST_BUILTINS_OBJS += test-read-cache.o\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 2cc18bbffd..eb583093aa 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -308,11 +308,14 @@ static void write_head_info(void)\n \tpacket_flush(1);\n }\n \n+#define RUN_PROC_RECEIVE_SCHEDULED\t1\n+#define RUN_PROC_RECEIVE_RETURNED\t2\n struct command {\n \tstruct command *next;\n \tconst char *error_string;\n \tunsigned int skip_update:1,\n-\t\t     did_not_exist:1;\n+\t\t     did_not_exist:1,\n+\t\t     run_proc_receive:2;\n \tint index;\n \tstruct object_id old_oid;\n \tstruct object_id new_oid;\n@@ -817,6 +820,234 @@ static int run_update_hook(struct command *cmd)\n \treturn finish_command(&proc);\n }\n \n+static struct command *find_command_by_refname(const struct command *list,\n+\t\t\t\t\t       const char *refname)\n+{\n+\tfor ( ; list; list = list->next)\n+\t\tif (!strcmp(list->ref_name, refname))\n+\t\t\treturn (struct command *)list;\n+\treturn NULL;\n+}\n+\n+static int read_proc_receive_result(struct packet_reader *reader,\n+\t\t\t\t    struct command *commands,\n+\t\t\t\t    struct strbuf *errmsg)\n+{\n+\tstruct command *hint;\n+\tstruct command *cmd;\n+\tint code = 0;\n+\n+\thint = NULL;\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\t\tchar *status;\n+\t\tchar *msg = NULL;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ') {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\\n\",\n+\t\t\t\t    reader->line);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\trefname = p;\n+\t\tstatus = strchr(p, ' ');\n+\t\tif (!status) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\\n\",\n+\t\t\t\t    reader->line);\n+\t\t\treturn -1;\n+\t\t}\n+\t\t*status++ = '\\0';\n+\t\tif (strlen(status) > 2 && *(status + 2) == ' ') {\n+\t\t\tmsg = status + 2;\n+\t\t\t*msg++ = '\\0';\n+\t\t}\n+\t\tif (strlen(status) != 2) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n+\t\t\t\t    status, reader->line);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\t/* first try searching at our hint, falling back to all refs */\n+\t\tif (hint)\n+\t\t\thint = find_command_by_refname(hint, refname);\n+\t\tif (!hint)\n+\t\t\thint = find_command_by_refname(commands, refname);\n+\t\tif (!hint) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unknown ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!hint->run_proc_receive) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on ref of builtin command: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcontinue;\n+\t\t}\n+\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n+\t\toidcpy(&hint->old_oid, &old_oid);\n+\t\toidcpy(&hint->new_oid, &new_oid);\n+\t\tif (!strcmp(status, \"ng\")) {\n+\t\t\tif (msg)\n+\t\t\t\thint->error_string = xstrdup(msg);\n+\t\t\telse\n+\t\t\t\thint->error_string = \"failed\";\n+\t\t\tcode = 1;\n+\t\t} else if (strcmp(\"ok\", status)) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n+\t\t\t\t    status, reader->line);\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+\n+\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\tif (cmd->run_proc_receive &&\n+\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED))\n+\t\t    cmd->error_string = \"no report from proc-receive\";\n+\n+\treturn code;\n+}\n+\n+static int run_proc_receive_hook(struct command *commands,\n+\t\t\t\t const struct string_list *push_options)\n+{\n+\tstruct child_process proc = CHILD_PROCESS_INIT;\n+\tstruct async muxer;\n+\tstruct command *cmd;\n+\tconst char *argv[2];\n+\tstruct packet_reader reader;\n+\tstruct strbuf cap = STRBUF_INIT;\n+\tstruct strbuf errmsg = STRBUF_INIT;\n+\tint pr_use_push_options = 0;\n+\tint version = 0;\n+\tint code;\n+\n+\targv[0] = find_hook(\"proc-receive\");\n+\tif (!argv[0]) {\n+\t\trp_error(\"cannot find hook 'proc-receive'\");\n+\t\treturn -1;\n+\t}\n+\targv[1] = NULL;\n+\n+\tproc.argv = argv;\n+\tproc.in = -1;\n+\tproc.out = -1;\n+\tproc.trace2_hook_name = \"proc-receive\";\n+\n+\tif (use_sideband) {\n+\t\tmemset(&muxer, 0, sizeof(muxer));\n+\t\tmuxer.proc = copy_to_sideband;\n+\t\tmuxer.in = -1;\n+\t\tcode = start_async(&muxer);\n+\t\tif (code)\n+\t\t\treturn code;\n+\t\tproc.err = muxer.in;\n+\t} else {\n+\t\tproc.err = 0;\n+\t}\n+\n+\tcode = start_command(&proc);\n+\tif (code) {\n+\t\tif (use_sideband)\n+\t\t\tfinish_async(&muxer);\n+\t\treturn code;\n+\t}\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\n+\t/* Version negotiaton */\n+\tpacket_reader_init(&reader, proc.out, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\tif (use_atomic)\n+\t\tstrbuf_addstr(&cap, \" atomic\");\n+\tif (use_push_options)\n+\t\tstrbuf_addstr(&cap, \" push-options\");\n+\tif (cap.len) {\n+\t\tpacket_write_fmt(proc.in, \"version=1%c%s\\n\", '\\0', cap.buf + 1);\n+\t\tstrbuf_release(&cap);\n+\t} else {\n+\t\tpacket_write_fmt(proc.in, \"version=1\\n\");\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader.pktlen > 8 && starts_with(reader.line, \"version=\")) {\n+\t\t\tversion = atoi(reader.line + 8);\n+\t\t\tlinelen = strlen(reader.line);\n+\t\t\tif (linelen < reader.pktlen) {\n+\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tpr_use_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (version != 1) {\n+\t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\\n\",\n+\t\t\t    version);\n+\t\tcode = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\t/* Send commands */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tchar *old_hex, *new_hex;\n+\n+\t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)\n+\t\t\tcontinue;\n+\t\told_hex = oid_to_hex(&cmd->old_oid);\n+\t\tnew_hex = oid_to_hex(&cmd->new_oid);\n+\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n+\t\t\t\t old_hex, new_hex, cmd->ref_name);\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\t/* Send push options */\n+\tif (pr_use_push_options) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tfor_each_string_list_item(item, push_options)\n+\t\t\tpacket_write_fmt(proc.in, \"%s\", item->string);\n+\t\tpacket_flush(proc.in);\n+\t}\n+\n+\t/* Read result from proc-receive */\n+\tcode = read_proc_receive_result(&reader, commands, &errmsg);\n+\n+cleanup:\n+\tclose(proc.in);\n+\tclose(proc.out);\n+\tif (use_sideband)\n+\t\tfinish_async(&muxer);\n+\tif (finish_command(&proc)) {\n+\t\tstrbuf_addstr(&errmsg, \"proc-receive did not exit properly\\n\");\n+\t\tcode = -1;\n+\t}\n+\tif (errmsg.len >0) {\n+\t\tchar *p = errmsg.buf;\n+\n+\t\tp += errmsg.len - 1;\n+\t\tif (*p == '\\n')\n+\t\t\t*p = '\\0';\n+\t\trp_error(\"%s\", errmsg.buf);\n+\t\tstrbuf_release(&errmsg);\n+\t}\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn code;\n+}\n+\n static char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n@@ -1392,7 +1623,7 @@ static void execute_commands_non_atomic(struct command *commands,\n \tstruct strbuf err = STRBUF_INIT;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\ttransaction = ref_transaction_begin(&err);\n@@ -1432,7 +1663,7 @@ static void execute_commands_atomic(struct command *commands,\n \t}\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\tcmd->error_string = update(cmd, si);\n@@ -1468,6 +1699,7 @@ static void execute_commands(struct command *commands,\n \tstruct iterate_data data;\n \tstruct async muxer;\n \tint err_fd = 0;\n+\tint run_proc_receive = 0;\n \n \tif (unpacker_error) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n@@ -1497,6 +1729,20 @@ static void execute_commands(struct command *commands,\n \n \treject_updates_to_hidden(commands);\n \n+\t/* Try to find commands that have special prefix in their reference names,\n+\t * and mark them to run an external \"proc-receive\" hook later.\n+\t */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!should_process_cmd(cmd))\n+\t\t\tcontinue;\n+\n+\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n+\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n+\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\trun_proc_receive = 1;\n+\t\t}\n+\t}\n+\n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n \t\t\tif (!cmd->error_string)\n@@ -1523,6 +1769,18 @@ static void execute_commands(struct command *commands,\n \tfree(head_name_to_free);\n \thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, NULL, NULL);\n \n+\tif (run_proc_receive) {\n+\t\tint code;\n+\n+\t\tcode = run_proc_receive_hook(commands, push_options);\n+\t\tif (code) {\n+\t\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\t\tif (!cmd->error_string  && (cmd->run_proc_receive || use_atomic))\n+\t\t\t\t\tcmd->error_string = \"fail to run proc-receive hook\";\n+\t\t\t}\n+\t\t}\n+\t}\n+\n \tif (use_atomic)\n \t\texecute_commands_atomic(commands, si);\n \telse\ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nnew file mode 100644\nindex 0000000000..ed30817905\n--- /dev/null\n+++ b/t/helper/test-proc-receive.c\n@@ -0,0 +1,172 @@\n+#include \"cache.h\"\n+#include \"connect.h\"\n+#include \"parse-options.h\"\n+#include \"pkt-line.h\"\n+#include \"string-list.h\"\n+#include \"test-tool.h\"\n+\n+static const char *proc_receive_usage[] = {\n+\t\"test-tool proc-receive [<options>...]\",\n+\tNULL\n+};\n+\n+static int version = 1;\n+static int verbose = 0;\n+static int no_push_options = 0;\n+static int use_atomic = 0;\n+static int use_push_options = 0;\n+static struct string_list returns = STRING_LIST_INIT_NODUP;\n+\n+struct command {\n+\tstruct command *next;\n+\tconst char *error_string;\n+\tunsigned int skip_update:1,\n+\t\t     did_not_exist:1;\n+\tint index;\n+\tstruct object_id old_oid;\n+\tstruct object_id new_oid;\n+\tchar ref_name[FLEX_ARRAY]; /* more */\n+};\n+\n+static void proc_receive_verison(struct packet_reader *reader) {\n+\tint server_version = 0;\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader->pktlen > 8 && starts_with(reader->line, \"version=\")) {\n+\t\t\tserver_version = atoi(reader->line+8);\n+\t\t\tlinelen = strlen(reader->line);\n+\t\t\tif (linelen < reader->pktlen) {\n+\t\t\t\tconst char *feature_list = reader->line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"atomic\"))\n+\t\t\t\t\tuse_atomic= 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tuse_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (server_version != 1)\n+\t\tdie(\"bad protocol version: %d\", server_version);\n+\n+\tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n+\t\t\t version, '\\0',\n+\t\t\t use_push_options && !no_push_options ? \"push-options\": \"\");\n+\tpacket_flush(1);\n+}\n+\n+static void proc_receive_read_commands(struct packet_reader *reader,\n+\t\t\t\t       struct command **commands)\n+{\n+\tstruct command **tail = commands;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tstruct command *cmd;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ')\n+\t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n+\t\t\t    reader->line);\n+\t\trefname = p;\n+\t\tFLEX_ALLOC_STR(cmd, ref_name, refname);\n+\t\toidcpy(&cmd->old_oid, &old_oid);\n+\t\toidcpy(&cmd->new_oid, &new_oid);\n+\n+\t\t*tail = cmd;\n+\t\ttail = &cmd->next;\n+\t}\n+}\n+\n+static void proc_receive_read_push_options(struct packet_reader *reader,\n+\t\t\t\t\t   struct string_list *options)\n+{\n+\n+\tif (no_push_options || !use_push_options)\n+\t       return;\n+\n+\twhile (1) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tstring_list_append(options, reader->line);\n+\t}\n+}\n+\n+int cmd__proc_receive(int argc, const char **argv)\n+{\n+\tstruct packet_reader reader;\n+\tstruct command *commands;\n+\tstruct string_list push_options = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"no-push-options\", &no_push_options,\n+\t\t\t \"disable push options\"),\n+\t\tOPT_STRING_LIST('r', \"return\", &returns, \"old/new/ref/status/msg\",\n+\t\t\t\t\"return of results\"),\n+\t\tOPT__VERBOSE(&verbose, \"be verbose\"),\n+\t\tOPT_INTEGER('V', \"version\", &version,\n+\t\t\t    \"use this protocol version number\"),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, \"test-tools\", options, proc_receive_usage, 0);\n+\tif (argc > 0)\n+\t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n+\n+\tpacket_reader_init(&reader, 0, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tproc_receive_verison(&reader);\n+\tproc_receive_read_commands(&reader, &commands);\n+\tproc_receive_read_push_options(&reader, &push_options);\n+\n+\tif (verbose) {\n+\t\tstruct command *cmd;\n+\n+\t\tif (use_push_options || use_atomic)\n+\t\t\tfprintf(stderr, \"proc-receive:%s%s\\n\",\n+\t\t\t\tuse_atomic? \" atomic\": \"\",\n+\t\t\t\tuse_push_options ? \" push_options\": \"\");\n+\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tchar *old_hex, *new_hex;\n+\n+\t\t\told_hex = oid_to_hex(&cmd->old_oid);\n+\t\t\tnew_hex = oid_to_hex(&cmd->new_oid);\n+\t\t\tfprintf(stderr, \"proc-receive< %s %s %s\\n\",\n+\t\t\t\told_hex, new_hex, cmd->ref_name);\n+\t\t}\n+\n+\t\tif (push_options.nr > 0) {\n+\t\t\tfor_each_string_list_item(item, &push_options)\n+\t\t\t\tfprintf(stderr, \"proc-receive< %s\\n\", item->string);\n+\t\t}\n+\n+\t\tif (returns.nr) {\n+\t\t\tfor_each_string_list_item(item, &returns)\n+\t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n+\t\t}\n+\t}\n+\n+\tif (returns.nr) {\n+\t\tfor_each_string_list_item(item, &returns)\n+\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n+\t}\n+\tpacket_flush(1);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 31eedcd241..f865433a61 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -44,6 +44,7 @@ static struct test_cmd cmds[] = {\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\n+\t{ \"proc-receive\", cmd__proc_receive},\n \t{ \"progress\", cmd__progress },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 4eb5e6609e..059008cf22 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -34,6 +34,7 @@ int cmd__parse_pathspec_file(int argc, const char** argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\n+int cmd__proc_receive(int argc, const char **argv);\n int cmd__progress(int argc, const char **argv);\n int cmd__reach(int argc, const char **argv);\n int cmd__read_cache(int argc, const char **argv);\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex 1784bcb584..a62dadb412 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -43,8 +43,11 @@ create_commits_in () {\n # Format the output of git-push, git-show-ref and other commands to make a\n # user-friendly and stable text.  We can easily prepare the expect text\n # without having to worry about future changes of the commit ID and spaces\n-# of the output.  We also replce single quotes with double quotes, because\n-# it is boring to prepare unquoted single quotes in expect txt.\n+# of the output.  Single quotes are replaced with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect txt.  We also\n+# remove some locale error messages, which break test if we turn on\n+# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n+# on plumbing commands.\n make_user_friendly_and_stable_output () {\n \tsed \\\n \t\t-e \"s/  *\\$//\" \\\n@@ -54,13 +57,16 @@ make_user_friendly_and_stable_output () {\n \t\t-e \"s/$B/<COMMIT-B>/g\" \\\n \t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n \t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n-\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\"\n+\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\" \\\n+\t\t-e \"s#To ../upstream.git#To <URL/of/upstream.git>#\" \\\n+\t\t-e \"/^error: / d\"\n }\n \n # Refs of upstream : master(B)  next(A)\n # Refs of workbench: master(A)           tags/v123\n test_expect_success \"setup\" '\n-\tgit init --bare upstream &&\n+\tupstream=upstream.git &&\n+\tgit init --bare \"$upstream\" &&\n \tgit init workbench &&\n \tcreate_commits_in workbench A B &&\n \t(\n@@ -68,7 +74,7 @@ test_expect_success \"setup\" '\n \t\t# Try to make a stable fixed width for abbreviated commit ID,\n \t\t# this fixed-width oid will be replaced with \"<OID>\".\n \t\tgit config core.abbrev 7 &&\n-\t\tgit remote add origin ../upstream &&\n+\t\tgit remote add origin ../$upstream &&\n \t\tgit update-ref refs/heads/master $A &&\n \t\tgit tag -m \"v123\" v123 $A &&\n \t\tgit push origin \\\n@@ -78,7 +84,7 @@ test_expect_success \"setup\" '\n \tTAG=$(git -C workbench rev-parse v123) &&\n \n \t# setup pre-receive hook\n-\tcat >upstream/hooks/pre-receive <<-\\EOF &&\n+\tcat >\"$upstream/hooks/pre-receive\" <<-\\EOF &&\n \t#!/bin/sh\n \n \techo >&2 \"# pre-receive hook\"\n@@ -90,7 +96,7 @@ test_expect_success \"setup\" '\n \tEOF\n \n \t# setup post-receive hook\n-\tcat >upstream/hooks/post-receive <<-\\EOF &&\n+\tcat >\"$upstream/hooks/post-receive\" <<-\\EOF &&\n \t#!/bin/sh\n \n \techo >&2 \"# post-receive hook\"\n@@ -102,8 +108,8 @@ test_expect_success \"setup\" '\n \tEOF\n \n \tchmod a+x \\\n-\t\tupstream/hooks/pre-receive \\\n-\t\tupstream/hooks/post-receive\n+\t\t\"$upstream/hooks/pre-receive\" \\\n+\t\t\"$upstream/hooks/post-receive\"\n '\n \n # Refs of upstream : master(B)  next(A)\n@@ -131,7 +137,7 @@ test_expect_success \"normal git-push command\" '\n \tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n-\tTo ../upstream\n+\tTo <URL/of/upstream.git>\n \t + <OID>...<OID> HEAD -> master (forced update)\n \t - [deleted] next\n \t * [new tag] v123 -> v123\n@@ -139,7 +145,7 @@ test_expect_success \"normal git-push command\" '\n \t * [new branch] HEAD -> a/b/c\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C upstream show-ref >out &&\n+\tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n \t<COMMIT-A> refs/heads/a/b/c\n@@ -150,4 +156,569 @@ test_expect_success \"normal git-push command\" '\n \ttest_cmp expect actual\n '\n \n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"no proc-receive hook, fail to push special ref\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic:                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"no proc-receive hook, fail all for atomic push\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad version)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive bad protocol: unknown version\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: fatal: the remote end hung up unexpectedly\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tremote: proc-receive did not exit properly\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_i18ncmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive bad protocol: no report\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad oid)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"bad-id new-id ref ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: bad oid\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> bad-id new-id ref ok\n+\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"bad-id new-id ref ok\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: no status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic xx msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: unknown status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic xx msg\n+\tremote: error: proc-receive has bad status \"xx\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic bad status\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: bad status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic bad status\n+\tremote: error: proc-receive has bad status \"bad status\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (no message)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (has message)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng error msg\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (report status on builtin command)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/heads/master ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: warning on report for builtin command\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/heads/master ok\n+\tremote: error: proc-receive reported status on ref of builtin command: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t <OID>..<OID> <COMMIT-B> -> master\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\n+\n+test_expect_success \"setup proc-receive hook (ok)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: no report from proc-receive\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"not support push options\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tfatal: the receiving end does not support push options\n+\tfatal: the remote end hung up unexpectedly\n+\tEOF\n+\ttest_i18ncmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"push with options\" '\n+\tgit -C workbench push \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394670","messageId":"20200403160838.6252-6-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqqftdl3fss.fsf@gitster.c.googlers.com","subject":"[PATCH v7 5/7] receive-pack: refactor report for proc-receive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-03T16:08:36Z","receivedAt":"2020-04-03T16:08:53Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThe \"proc-receive\" may update one or more references, and will send its\nresult one by one in pkt-line format.  Each line of the result has four\nfields and one optional message field, as \"<old-oid> <new-oid> <ref>\n<status> [<message>]\".  See the following example:\n\n    # OK, run this command successfully.\n    PKT-LINE(old-oid new-oid ref ok)\n\n    # NO, I reject it.\n    PKT-LINE(old-oid new-oid ref ng reason)\n\n    # OK, but use an alternate reference.\n    PKT-LINE(old-oid new-oid ref ok ref:alt-ref)\n\n    # It will fallthrough to receive-pack to execute.\n    PKT-LINE(old-oid new-oid ref ft)\n\nThe first three fields have the same foramt as a command.\n\nThe forth field has a two-letter status code.  Available status code:\n\n* ok: The command runs successfully.  If the optional message has a\n  prefix \"ref:\", the hook has created/updated an alternate reference\n  instead.\n\n* ng: Fail to run the command. Error message is in the optional message\n  field.\n\n* ft: Will fallthrough to receive-pack to execute.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c       | 21 +++++++--\n t/t5411-proc-receive-hook.sh | 90 +++++++++++++++++++++++++++++++++---\n transport-helper.c           | 64 ++++++++++++-------------\n transport.c                  | 59 +++++++++++++----------\n 4 files changed, 168 insertions(+), 66 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 8540829b05..47138e8fa8 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -328,6 +328,7 @@ static void write_head_info(void)\n struct command {\n \tstruct command *next;\n \tconst char *error_string;\n+\tconst char *extra_string;\n \tunsigned int skip_update:1,\n \t\t     did_not_exist:1,\n \t\t     run_proc_receive:2;\n@@ -913,7 +914,12 @@ static int read_proc_receive_result(struct packet_reader *reader,\n \t\t\telse\n \t\t\t\thint->error_string = \"failed\";\n \t\t\tcode = 1;\n-\t\t} else if (strcmp(\"ok\", status)) {\n+\t\t} else if (!strcmp(\"ok\", status)) {\n+\t\t\thint->extra_string = xstrdup_or_null(msg);\n+\t\t} else if (!strcmp(\"ft\", status)) {\n+\t\t\t/* Reset \"run_proc_receive\" field, and continue to run in \"receive-pack\" */\n+\t\t\thint->run_proc_receive = 0;\n+\t\t} else {\n \t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n \t\t\t\t    status, reader->line);\n \t\t\treturn -1;\n@@ -2183,12 +2189,17 @@ static void report(struct command *commands, const char *unpack_status)\n \tpacket_buf_write(&buf, \"unpack %s\\n\",\n \t\t\t unpack_status ? unpack_status : \"ok\");\n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!cmd->error_string)\n-\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n-\t\t\t\t\t cmd->ref_name);\n-\t\telse\n+\t\tif (!cmd->error_string) {\n+\t\t\tif (!cmd->extra_string)\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n+\t\t\t\t\t\t cmd->ref_name);\n+\t\t\telse\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s%c%s\\n\",\n+\t\t\t\t\t\t cmd->ref_name, ' ', cmd->extra_string);\n+\t\t} else {\n \t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n \t\t\t\t\t cmd->ref_name, cmd->error_string);\n+\t\t}\n \t}\n \tpacket_buf_flush(&buf);\n \ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex cc5f1adda4..07a3742068 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -743,8 +743,8 @@ test_expect_success \"setup proc-receive hook\" '\n \n \ttest-tool proc-receive -v \\\n \t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n-\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n-\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok ref:refs/pull/123/head\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok ref:refs/pull/124/head\"\n \tEOF\n \tchmod a+x \"$upstream/hooks/proc-receive\"\n '\n@@ -769,16 +769,16 @@ test_expect_success \"report update of all special refs\" '\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n-\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n-\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok ref:refs/pull/123/head\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok ref:refs/pull/124/head\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \t * [new reference] HEAD -> refs/review/a/b/c/topic\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/pull/124/head\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -807,6 +807,7 @@ test_expect_success \"setup proc-receive hook\" '\n # git push         :                       bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n test_expect_success \"report mixed refs update\" '\n \tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n \t\tHEAD:refs/heads/bar \\\n \t\tHEAD:refs/heads/baz \\\n \t\tHEAD:refs/for/next/topic \\\n@@ -816,6 +817,7 @@ test_expect_success \"report mixed refs update\" '\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n@@ -827,12 +829,14 @@ test_expect_success \"report mixed refs update\" '\n \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n \tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n+\t <OID>..<OID> <COMMIT-B> -> master\n \t * [new branch] HEAD -> bar\n \t * [new branch] HEAD -> baz\n \t * [new reference] HEAD -> refs/for/next/topic\n@@ -846,6 +850,80 @@ test_expect_success \"report mixed refs update\" '\n \t<COMMIT-A> refs/heads/bar\n \t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"config receive.procReceiveRefs for all ref/\" '\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs/\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$B $A refs/heads/master ft\" \\\n+\t\t-r \"$A $ZERO_OID refs/heads/foo ft\" \\\n+\t\t-r \"$A $B refs/heads/bar ft\" \\\n+\t\t-r \"$A $B refs/for/master/topic ok ref:refs/pull/123/head\" \\\n+\t\t-r \"$B $A refs/for/next/topic ok ref:refs/pull/124/head\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      :                       (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"report test: fallthrough\" '\n+\tgit -C workbench push -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/heads/master ft\n+\tremote: proc-receive> <COMMIT-A> <ZERO-OID> refs/heads/foo ft\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/heads/bar ft\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/for/master/topic ok ref:refs/pull/123/head\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/next/topic ok ref:refs/pull/124/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/next/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID>..<OID> <COMMIT-B> -> bar\n+\t - [deleted] foo\n+\t + <OID>...<OID> HEAD -> master (forced update)\n+\t * [new reference] HEAD -> refs/pull/123/head\n+\t * [new reference] HEAD -> refs/pull/124/head\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/master\n \tEOF\n \ttest_cmp expect actual\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 20a7185ec4..cec3495d59 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -747,37 +747,39 @@ static int push_update_ref_status(struct strbuf *buf,\n \t\t\tmsg = xstrdup(msg);\n \t\tstrbuf_release(&msg_buf);\n \n-\t\tif (!strcmp(msg, \"no match\")) {\n-\t\t\tstatus = REF_STATUS_NONE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"up to date\")) {\n-\t\t\tstatus = REF_STATUS_UPTODATE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"non-fast forward\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_NONFASTFORWARD;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"already exists\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_ALREADY_EXISTS;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"fetch first\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_FETCH_FIRST;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"needs force\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_NEEDS_FORCE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"stale info\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_STALE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"forced update\")) {\n-\t\t\tforced = 1;\n-\t\t\tFREE_AND_NULL(msg);\n+\t\tif (status != REF_STATUS_OK) {\n+\t\t\tif (!strcmp(msg, \"no match\")) {\n+\t\t\t\tstatus = REF_STATUS_NONE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"up to date\")) {\n+\t\t\t\tstatus = REF_STATUS_UPTODATE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"non-fast forward\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_NONFASTFORWARD;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"already exists\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_ALREADY_EXISTS;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"fetch first\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_FETCH_FIRST;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"needs force\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_NEEDS_FORCE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"stale info\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_STALE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"forced update\")) {\n+\t\t\t\tforced = 1;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n \t\t}\n \t}\n \ndiff --git a/transport.c b/transport.c\nindex 272c0f4046..28731fa014 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -459,15 +459,18 @@ void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int v\n \t}\n }\n \n-static void print_ref_status(char flag, const char *summary,\n+static void print_ref_status(char flag, const char *summary, char *target_refname,\n \t\t\t     struct ref *to, struct ref *from, const char *msg,\n \t\t\t     int porcelain, int summary_width)\n {\n+\tif (!target_refname)\n+\t\ttarget_refname = to->name;\n+\n \tif (porcelain) {\n \t\tif (from)\n-\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, target_refname);\n \t\telse\n-\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, target_refname);\n \t\tif (msg)\n \t\t\tfprintf(stdout, \"%s (%s)\\n\", summary, msg);\n \t\telse\n@@ -481,9 +484,9 @@ static void print_ref_status(char flag, const char *summary,\n \t\tfprintf(stderr, \" %s%c %-*s%s \", red, flag, summary_width,\n \t\t\tsummary, reset);\n \t\tif (from)\n-\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(to->name));\n+\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(target_refname));\n \t\telse\n-\t\t\tfputs(prettify_refname(to->name), stderr);\n+\t\t\tfputs(prettify_refname(target_refname), stderr);\n \t\tif (msg) {\n \t\t\tfputs(\" (\", stderr);\n \t\t\tfputs(msg, stderr);\n@@ -495,18 +498,26 @@ static void print_ref_status(char flag, const char *summary,\n \n static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_width)\n {\n+\tchar *refname;\n+\n+\tif (ref->remote_status && !strncmp(ref->remote_status, \"ref:\", 4))\n+\t\trefname = ref->remote_status + 4;\n+\telse\n+\t\trefname = ref->name;\n+\n \tif (ref->deletion)\n-\t\tprint_ref_status('-', \"[deleted]\", ref, NULL, NULL,\n+\t\tprint_ref_status('-', \"[deleted]\", refname, ref, NULL, NULL,\n \t\t\t\t porcelain, summary_width);\n-\telse if (is_null_oid(&ref->old_oid))\n+\telse if (is_null_oid(&ref->old_oid)) {\n+\n \t\tprint_ref_status('*',\n-\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t (starts_with(refname, \"refs/tags/\")\n \t\t\t\t  ? \"[new tag]\"\n-\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t  : (starts_with(refname, \"refs/heads/\")\n \t\t\t\t     ? \"[new branch]\"\n \t\t\t\t     : \"[new reference]\")),\n-\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n-\telse {\n+\t\t\t\t refname, ref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t} else {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n \t\tconst char *msg;\n@@ -525,7 +536,7 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\tstrbuf_add_unique_abbrev(&quickref, &ref->new_oid,\n \t\t\t\t\t DEFAULT_ABBREV);\n \n-\t\tprint_ref_status(type, quickref.buf, ref, ref->peer_ref, msg,\n+\t\tprint_ref_status(type, quickref.buf, refname, ref, ref->peer_ref, msg,\n \t\t\t\t porcelain, summary_width);\n \t\tstrbuf_release(&quickref);\n \t}\n@@ -542,56 +553,56 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count,\n \n \tswitch(ref->status) {\n \tcase REF_STATUS_NONE:\n-\t\tprint_ref_status('X', \"[no match]\", ref, NULL, NULL,\n+\t\tprint_ref_status('X', \"[no match]\", NULL, ref, NULL, NULL,\n \t\t\t\t porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NODELETE:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, NULL,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, NULL,\n \t\t\t\t \"remote does not support deleting refs\",\n \t\t\t\t porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_UPTODATE:\n-\t\tprint_ref_status('=', \"[up to date]\", ref,\n+\t\tprint_ref_status('=', \"[up to date]\", NULL, ref,\n \t\t\t\t ref->peer_ref, NULL, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NONFASTFORWARD:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"non-fast-forward\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"already exists\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_FETCH_FIRST:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"fetch first\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NEEDS_FORCE:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"needs force\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_STALE:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"stale info\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_SHALLOW:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"new shallow roots not allowed\",\n \t\t\t\t porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REMOTE_REJECT:\n-\t\tprint_ref_status('!', \"[remote rejected]\", ref,\n+\t\tprint_ref_status('!', \"[remote rejected]\", NULL, ref,\n \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n \t\t\t\t ref->remote_status, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_EXPECTING_REPORT:\n-\t\tprint_ref_status('!', \"[remote failure]\", ref,\n+\t\tprint_ref_status('!', \"[remote failure]\", NULL, ref,\n \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n \t\t\t\t \"remote failed to report status\",\n \t\t\t\t porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_ATOMIC_PUSH_FAILED:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"atomic push failed\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_OK:\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394672","messageId":"20200403160838.6252-5-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqqftdl3fss.fsf@gitster.c.googlers.com","subject":"[PATCH v7 4/7] receive-pack: new config receive.procReceiveRefs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-03T16:08:35Z","receivedAt":"2020-04-03T16:08:54Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd a new multi-valued config variable \"receive.procReceiveRefs\"\nfor `receive-pack` command, like the follows:\n\n    git config --system --add receive.procReceiveRefs refs/for/\n    git config --system --add receive.procReceiveRefs refs/drafts/\n\nIf the specific prefix strings match the reference names of the commands\nwhich are sent by git client to `receive-pack`, these commands will be\nexecuted by an external hook (named \"proc-receive\"), instead of the\ninternal `execute_commands` function.\n\nFor example, if it is set to \"refs/for/\", pushing to a reference such as\n\"refs/for/master\" will not create or update reference \"refs/for/master\",\nbut may create or update a pull request directly by running the external\nhook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/config/receive.txt |  14 ++++\n builtin/receive-pack.c           |  43 ++++++++--\n t/t5411-proc-receive-hook.sh     | 130 +++++++++++++++++++++++++++++++\n 3 files changed, 180 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/receive.txt b/Documentation/config/receive.txt\nindex 65f78aac37..0178f2d478 100644\n--- a/Documentation/config/receive.txt\n+++ b/Documentation/config/receive.txt\n@@ -114,6 +114,20 @@ receive.hideRefs::\n \tAn attempt to update or delete a hidden ref by `git push` is\n \trejected.\n \n+receive.procReceiveRefs::\n+\tThis is a multi-valued variable that defines reference prefixes\n+\tto match the commands in `receive-pack`.  Commands matching the\n+\tprefixes will be executed by an external hooks \"proc-receive\",\n+\tinstead of the internal `execute_commands` function.  If this\n+\tvariable is not defined, the \"proc-receive\" hook will never be\n+\tused, and all commands will be executed by the internal\n+\t`execute_commands` function.\n+\n+\tFor example, if this variable is set to \"refs/for/\", pushing to\n+\treference such as \"refs/for/master\" will not create or update a\n+\treference named \"refs/for/master\", but may create or update a\n+\tpull request directly by running an external hook.\n+\n receive.updateServerInfo::\n \tIf set to true, git-receive-pack will run git-update-server-info\n \tafter receiving data from git-push and updating refs.\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex eb583093aa..8540829b05 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -76,6 +76,7 @@ static struct object_id push_cert_oid;\n static struct signature_check sigcheck;\n static const char *push_cert_nonce;\n static const char *cert_nonce_seed;\n+static struct string_list proc_receive_refs;\n \n static const char *NONCE_UNSOLICITED = \"UNSOLICITED\";\n static const char *NONCE_BAD = \"BAD\";\n@@ -228,6 +229,20 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (strcmp(var, \"receive.procreceiverefs\") == 0) {\n+\t\tchar *prefix;\n+\t\tint len;\n+\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tprefix = xstrdup(value);\n+\t\tlen = strlen(prefix);\n+\t\twhile (len && prefix[len - 1] == '/')\n+\t\t\tprefix[--len] = '\\0';\n+\t\tstring_list_insert(&proc_receive_refs, prefix);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -1732,15 +1747,26 @@ static void execute_commands(struct command *commands,\n \t/* Try to find commands that have special prefix in their reference names,\n \t * and mark them to run an external \"proc-receive\" hook later.\n \t */\n-\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n-\t\t\tcontinue;\n+\tif (proc_receive_refs.nr > 0) {\n+\t\tstruct strbuf refname_full = STRBUF_INIT;\n+\t\tsize_t prefix_len;\n+\n+\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n+\t\tprefix_len = refname_full.len;\n \n-\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n-\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n-\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n-\t\t\trun_proc_receive = 1;\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tif (!should_process_cmd(cmd))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_setlen(&refname_full, prefix_len);\n+\t\t\tstrbuf_addstr(&refname_full, cmd->ref_name);\n+\t\t\tif (ref_is_matched(&proc_receive_refs, cmd->ref_name, refname_full.buf)) {\n+\t\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\t\trun_proc_receive = 1;\n+\t\t\t}\n \t\t}\n+\n+\t\tstrbuf_release(&refname_full);\n \t}\n \n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n@@ -2200,6 +2226,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n+\tstring_list_init(&proc_receive_refs, 0);\n+\n \tpacket_trace_identity(\"receive-pack\");\n \n \targc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);\n@@ -2315,5 +2343,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \toid_array_clear(&shallow);\n \toid_array_clear(&ref);\n \tfree((void *)push_cert_nonce);\n+\tstring_list_clear(&proc_receive_refs, 0);\n \treturn 0;\n }\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex a62dadb412..cc5f1adda4 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -167,6 +167,14 @@ test_expect_success \"cleanup\" '\n \t)\n '\n \n+test_expect_success \"add two receive.procReceiveRefs settings\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit config --add receive.procReceiveRefs refs/for/ &&\n+\t\tgit config --add receive.procReceiveRefs refs/review/\n+\t)\n+'\n+\n # Refs of upstream : master(A)\n # Refs of workbench: master(A)  tags/v123\n # git push         :                       next(A)  refs/for/master/topic(A)\n@@ -721,4 +729,126 @@ test_expect_success \"push with options\" '\n \ttest_cmp expect actual\n '\n \n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/review/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"report update of all special refs\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/review/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/review/a/b/c/topic\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"report mixed refs update\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> bar\n+\t * [new branch] HEAD -> baz\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new branch] HEAD -> foo\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394675","messageId":"20200403160838.6252-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqqftdl3fss.fsf@gitster.c.googlers.com","subject":"[PATCH v7 0/7] New proc-receive hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-03T16:08:31Z","receivedAt":"2020-04-03T16:08:54Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\n## Changes since v6\n\n* Test breaks because of the conflict between `copy_to_sideband()` and\n  `rp_error()`.  Store error messages in `errmsg` and output the errmsg\n  after sideband muxer closed.\n\n* Use test_i18ncmp instead of mark the whole test case using\n  C_LOCALE_OUTPUT.\n\n* Depends on topic `jx/atomic-push`.  One test case will fail without\n  it.\n\n## Range-diff v6...v7\n\n1:  ed8d2c0e82 ! 1:  4259d31426 receive-pack: add new proc-receive hook\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +}\n     +\n     +static int read_proc_receive_result(struct packet_reader *reader,\n    -+\t\t\t\t    struct command *commands)\n    ++\t\t\t\t    struct command *commands,\n    ++\t\t\t\t    struct strbuf *errmsg)\n     +{\n     +\tstruct command *hint;\n     +\tstruct command *cmd;\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\t    *p++ != ' ' ||\n     +\t\t    parse_oid_hex(p, &new_oid, &p) ||\n     +\t\t    *p++ != ' ') {\n    -+\t\t\trp_error(\"protocol error: proc-receive expected 'old new ref status [msg]', got '%s'\",\n    -+\t\t\t\t reader->line);\n    ++\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\\n\",\n    ++\t\t\t\t    reader->line);\n     +\t\t\treturn -1;\n     +\t\t}\n     +\n     +\t\trefname = p;\n     +\t\tstatus = strchr(p, ' ');\n     +\t\tif (!status) {\n    -+\t\t\trp_error(\"protocol error: proc-receive expected 'old new ref status [msg]', got '%s'\",\n    -+\t\t\t\t reader->line);\n    ++\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\\n\",\n    ++\t\t\t\t    reader->line);\n     +\t\t\treturn -1;\n     +\t\t}\n     +\t\t*status++ = '\\0';\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\t\t*msg++ = '\\0';\n     +\t\t}\n     +\t\tif (strlen(status) != 2) {\n    -+\t\t\trp_error(\"protocol error: proc-receive has bad status '%s' for '%s'\",\n    -+\t\t\t\t status, reader->line);\n    ++\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n    ++\t\t\t\t    status, reader->line);\n     +\t\t\treturn -1;\n     +\t\t}\n     +\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\tif (!hint)\n     +\t\t\thint = find_command_by_refname(commands, refname);\n     +\t\tif (!hint) {\n    -+\t\t\twarning(\"proc-receive reported status on unknown ref: %s\",\n    -+\t\t\t\trefname);\n    ++\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unknown ref: %s\\n\",\n    ++\t\t\t\t    refname);\n     +\t\t\tcontinue;\n     +\t\t}\n     +\t\tif (!hint->run_proc_receive) {\n    -+\t\t\twarning(\"proc-receive reported status on ref of builtin command: %s\",\n    -+\t\t\t\trefname);\n    ++\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on ref of builtin command: %s\\n\",\n    ++\t\t\t\t    refname);\n     +\t\t\tcontinue;\n     +\t\t}\n     +\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\t\t\thint->error_string = \"failed\";\n     +\t\t\tcode = 1;\n     +\t\t} else if (strcmp(\"ok\", status)) {\n    -+\t\t\trp_error(\"protocol error: proc-receive has bad status '%s' for '%s'\",\n    -+\t\t\t\t status, reader->line);\n    ++\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n    ++\t\t\t\t    status, reader->line);\n     +\t\t\treturn -1;\n     +\t\t}\n     +\t}\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\tconst char *argv[2];\n     +\tstruct packet_reader reader;\n     +\tstruct strbuf cap = STRBUF_INIT;\n    ++\tstruct strbuf errmsg = STRBUF_INIT;\n     +\tint pr_use_push_options = 0;\n     +\tint version = 0;\n     +\tint code;\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t}\n     +\n     +\tif (version != 1) {\n    -+\t\trp_error(\"protocol error: unknown proc-receive version '%d'\",\n    -+\t\t\t version);\n    ++\t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\\n\",\n    ++\t\t\t    version);\n     +\t\tcode = -1;\n     +\t\tgoto cleanup;\n     +\t}\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t}\n     +\n     +\t/* Read result from proc-receive */\n    -+\tcode = read_proc_receive_result(&reader, commands);\n    ++\tcode = read_proc_receive_result(&reader, commands, &errmsg);\n     +\n     +cleanup:\n     +\tclose(proc.in);\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\tif (use_sideband)\n     +\t\tfinish_async(&muxer);\n     +\tif (finish_command(&proc)) {\n    -+\t\trp_error(\"proc-receive did not exit properly\");\n    ++\t\tstrbuf_addstr(&errmsg, \"proc-receive did not exit properly\\n\");\n     +\t\tcode = -1;\n     +\t}\n    ++\tif (errmsg.len >0) {\n    ++\t\tchar *p = errmsg.buf;\n    ++\n    ++\t\tp += errmsg.len - 1;\n    ++\t\tif (*p == '\\n')\n    ++\t\t\t*p = '\\0';\n    ++\t\trp_error(\"%s\", errmsg.buf);\n    ++\t\tstrbuf_release(&errmsg);\n    ++\t}\n     +\tsigchain_pop(SIGPIPE);\n    ++\n     +\treturn code;\n     +}\n     +\n    @@ t/t5411-proc-receive-hook.sh: make_user_friendly_and_stable_output () {\n     +\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\" \\\n     +\t\t-e \"s#To ../upstream.git#To <URL/of/upstream.git>#\" \\\n     +\t\t-e \"/^error: / d\"\n    -+}\n    -+\n    -+# Asynchronous sideband may generate inconsistent output messages,\n    -+# sort before comparison.\n    -+test_sorted_cmp () {\n    -+\tif ! $GIT_TEST_CMP \"$@\" >/dev/null 2>&1\n    -+\tthen\n    -+\t\tcmd=$GIT_TEST_CMP\n    -+\t\tfor f in \"$@\"\n    -+\t\tdo\n    -+\t\t\tsort \"$f\" >\"$f.sorted\"\n    -+\t\t\tcmd=\"$cmd \\\"$f.sorted\\\"\"\n    -+\t\tdone\n    -+\t\tif ! eval $cmd >/dev/null 2>&1\n    -+\t\tthen\n    -+\t\t\t$GIT_TEST_CMP \"$@\"\n    -+\t\tfi\n    -+\tfi\n      }\n      \n      # Refs of upstream : master(B)  next(A)\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\t * [new branch] HEAD -> next\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\tEOF\n    -+\ttest_sorted_cmp expect actual &&\n    ++\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push --atomic:                       next(A)  refs/for/master/topic(A)\n    -+test_expect_failure \"no proc-receive hook, fail all for atomic push\" '\n    ++test_expect_success \"no proc-receive hook, fail all for atomic push\" '\n     +\ttest_must_fail git -C workbench push --atomic origin \\\n     +\t\tHEAD:next \\\n     +\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\tEOF\n    -+\ttest_sorted_cmp expect actual &&\n    ++\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic(A)\n    -+test_expect_success C_LOCALE_OUTPUT \"proc-receive bad protocol: unknown version\" '\n    ++test_expect_success \"proc-receive bad protocol: unknown version\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: # pre-receive hook\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n    -+\tremote: error: protocol error: unknown proc-receive version \"2\"\n     +\tremote: fatal: the remote end hung up unexpectedly\n    -+\tremote: error: proc-receive did not exit properly\n    ++\tremote: error: proc-receive version \"2\" is not supported\n    ++\tremote: proc-receive did not exit properly\n     +\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\tEOF\n    -+\ttest_sorted_cmp expect actual &&\n    ++\ttest_i18ncmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> bad-id new-id ref ok\n    -+\tremote: error: protocol error: proc-receive expected \"old new ref status [msg]\", got \"bad-id new-id ref ok\"\n    ++\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"bad-id new-id ref ok\"\n     +\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\tEOF\n    -+\ttest_sorted_cmp expect actual &&\n    ++\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: error: protocol error: proc-receive expected \"old new ref status [msg]\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n    ++\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n     +\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\tEOF\n    -+\ttest_sorted_cmp expect actual &&\n    ++\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic xx msg\n    -+\tremote: error: protocol error: proc-receive has bad status \"xx\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n    ++\tremote: error: proc-receive has bad status \"xx\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n     +\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\tEOF\n    -+\ttest_sorted_cmp expect actual &&\n    ++\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic bad status\n    -+\tremote: error: protocol error: proc-receive has bad status \"bad status\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n    ++\tremote: error: proc-receive has bad status \"bad status\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n     +\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\tEOF\n    -+\ttest_sorted_cmp expect actual &&\n    ++\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/heads/master ok\n    -+\twarning: proc-receive reported status on ref of builtin command: refs/heads/master\n    ++\tremote: error: proc-receive reported status on ref of builtin command: refs/heads/master\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n     +\tTo <URL/of/upstream.git>\n     +\t <OID>..<OID> <COMMIT-B> -> master\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n     +\tEOF\n    -+\ttest_sorted_cmp expect actual &&\n    ++\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n    -+\twarning: proc-receive reported status on unknown ref: refs/for/master/topic\n    ++\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (no report from proc-receive)\n     +\tEOF\n    -+\ttest_sorted_cmp expect actual &&\n    ++\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push -o ...  :                       refs/for/master/topic\n    -+test_expect_success C_LOCALE_OUTPUT \"not support push options\" '\n    ++test_expect_success \"not support push options\" '\n     +\ttest_must_fail git -C workbench push \\\n     +\t\t-o issue=123 \\\n     +\t\t-o reviewer=user1 \\\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tfatal: the receiving end does not support push options\n     +\tfatal: the remote end hung up unexpectedly\n     +\tEOF\n    -+\ttest_cmp expect actual &&\n    ++\ttest_i18ncmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n2:  1aa84fdf6f = 2:  b47379a7fe refs.c: refactor to reuse ref_is_hidden()\n3:  a301edb86c = 3:  461f3a4141 receive-pack: new config receive.procReceiveRefs\n4:  538fa71f48 ! 4:  82e6d94b0b receive-pack: refactor report for proc-receive\n    @@ builtin/receive-pack.c: static int read_proc_receive_result(struct packet_reader\n     +\t\t\t/* Reset \"run_proc_receive\" field, and continue to run in \"receive-pack\" */\n     +\t\t\thint->run_proc_receive = 0;\n     +\t\t} else {\n    - \t\t\trp_error(\"protocol error: proc-receive has bad status '%s' for '%s'\",\n    - \t\t\t\t status, reader->line);\n    + \t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n    + \t\t\t\t    status, reader->line);\n      \t\t\treturn -1;\n     @@ builtin/receive-pack.c: static void report(struct command *commands, const char *unpack_status)\n      \tpacket_buf_write(&buf, \"unpack %s\\n\",\n5:  20289547f3 ! 5:  f90bacec1d t5412: test proc-receive hook on HTTP protocol\n    @@ t/t5412-proc-receive-hook-http-protocol.sh (new)\n     +\t\t-e \"/^remote: fatal: /d\"\n     +}\n     +\n    -+# Asynchronous sideband may generate inconsistent output messages,\n    -+# sort before comparison.\n    -+test_sorted_cmp () {\n    -+\tif ! $GIT_TEST_CMP \"$@\" >/dev/null 2>&1\n    -+\tthen\n    -+\t\tcmd=$GIT_TEST_CMP\n    -+\t\tfor f in \"$@\"\n    -+\t\tdo\n    -+\t\t\tsort \"$f\" >\"$f.sorted\"\n    -+\t\t\tcmd=\"$cmd \\\"$f.sorted\\\"\"\n    -+\t\tdone\n    -+\t\tif ! eval $cmd >/dev/null 2>&1\n    -+\t\tthen\n    -+\t\t\t$GIT_TEST_CMP \"$@\"\n    -+\t\tfi\n    -+\tfi\n    -+}\n    -+\n     +# Refs of upstream : master(B)  next(A)\n     +# Refs of workbench: master(A)           tags/v123\n     +test_expect_success \"setup\" '\n    @@ t/t5412-proc-receive-hook-http-protocol.sh (new)\n     +\t * [new branch] HEAD -> next\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\tEOF\n    -+\ttest_sorted_cmp expect actual &&\n    ++\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5412-proc-receive-hook-http-protocol.sh (new)\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push --atomic:                       next(A)  refs/for/master/topic(A)\n    -+test_expect_failure \"no proc-receive hook, fail all for atomic push\" '\n    ++test_expect_success \"no proc-receive hook, fail all for atomic push\" '\n     +\ttest_must_fail git -C workbench push --atomic origin \\\n     +\t\tHEAD:next \\\n     +\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n    @@ t/t5412-proc-receive-hook-http-protocol.sh (new)\n     +\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\tEOF\n    -+\ttest_sorted_cmp expect actual &&\n    ++\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5412-proc-receive-hook-http-protocol.sh (new)\n     +\tremote: # pre-receive hook\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n    -+\tremote: error: protocol error: unknown proc-receive version \"2\"\n    -+\tremote: error: proc-receive did not exit properly\n    ++\tremote: error: proc-receive version \"2\" is not supported\n    ++\tremote: proc-receive did not exit properly\n     +\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\tEOF\n    -+\ttest_sorted_cmp expect actual &&\n    ++\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5412-proc-receive-hook-http-protocol.sh (new)\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> bad-id new-id ref ok\n    -+\tremote: error: protocol error: proc-receive expected \"old new ref status [msg]\", got \"bad-id new-id ref ok\"\n    ++\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"bad-id new-id ref ok\"\n     +\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\tEOF\n    -+\ttest_sorted_cmp expect actual &&\n    ++\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5412-proc-receive-hook-http-protocol.sh (new)\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: error: protocol error: proc-receive expected \"old new ref status [msg]\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n    ++\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n     +\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\tEOF\n    -+\ttest_sorted_cmp expect actual &&\n    ++\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5412-proc-receive-hook-http-protocol.sh (new)\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic xx msg\n    -+\tremote: error: protocol error: proc-receive has bad status \"xx\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n    ++\tremote: error: proc-receive has bad status \"xx\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n     +\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\tEOF\n    -+\ttest_sorted_cmp expect actual &&\n    ++\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5412-proc-receive-hook-http-protocol.sh (new)\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic bad status\n    -+\tremote: error: protocol error: proc-receive has bad status \"bad status\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n    ++\tremote: error: proc-receive has bad status \"bad status\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n     +\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\tEOF\n    -+\ttest_sorted_cmp expect actual &&\n    ++\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5412-proc-receive-hook-http-protocol.sh (new)\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/heads/master ok\n    ++\tremote: error: proc-receive reported status on ref of builtin command: refs/heads/master\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n     +\tTo <URL/of/upstream.git>\n     +\t <OID>..<OID> <COMMIT-B> -> master\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n     +\tEOF\n    -+\ttest_sorted_cmp expect actual &&\n    ++\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5412-proc-receive-hook-http-protocol.sh (new)\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n    ++\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (no report from proc-receive)\n     +\tEOF\n    -+\ttest_sorted_cmp expect actual &&\n    ++\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5412-proc-receive-hook-http-protocol.sh (new)\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push -o ...  :                       refs/for/master/topic\n    -+test_expect_success C_LOCALE_OUTPUT \"not support push options\" '\n    ++test_expect_success \"not support push options\" '\n     +\ttest_must_fail git -C workbench push \\\n     +\t\t-o issue=123 \\\n     +\t\t-o reviewer=user1 \\\n    @@ t/t5412-proc-receive-hook-http-protocol.sh (new)\n     +\tfatal: the receiving end does not support push options\n     +\tfatal: the remote end hung up unexpectedly\n     +\tEOF\n    -+\ttest_cmp expect actual &&\n    ++\ttest_i18ncmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n6:  da9106a42d = 6:  5338d358c3 doc: add documentation for the proc-receive hook\n\n---\n\nJiang Xin (7):\n  transport: not report a non-head push as a branch\n  receive-pack: add new proc-receive hook\n  refs.c: refactor to reuse ref_is_hidden()\n  receive-pack: new config receive.procReceiveRefs\n  receive-pack: refactor report for proc-receive\n  t5412: test proc-receive hook on HTTP protocol\n  doc: add documentation for the proc-receive hook\n\n Documentation/config/receive.txt           |  14 +\n Documentation/githooks.txt                 |  70 ++\n Makefile                                   |   1 +\n builtin/receive-pack.c                     | 312 ++++++-\n refs.c                                     |  11 +-\n refs.h                                     |   1 +\n t/helper/test-proc-receive.c               | 172 ++++\n t/helper/test-tool.c                       |   1 +\n t/helper/test-tool.h                       |   1 +\n t/t5411-proc-receive-hook.sh               | 932 ++++++++++++++++++++\n t/t5412-proc-receive-hook-http-protocol.sh | 944 +++++++++++++++++++++\n t/t5516-fetch-push.sh                      |   2 +-\n transport-helper.c                         |  64 +-\n transport.c                                |  62 +-\n 14 files changed, 2521 insertions(+), 66 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100755 t/t5412-proc-receive-hook-http-protocol.sh\n\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394673","messageId":"20200403160838.6252-8-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqqftdl3fss.fsf@gitster.c.googlers.com","subject":"[PATCH v7 7/7] doc: add documentation for the proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-03T16:08:38Z","receivedAt":"2020-04-03T16:08:56Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd documentation for the new \"proc-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/githooks.txt | 70 ++++++++++++++++++++++++++++++++++++++\n 1 file changed, 70 insertions(+)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 3dccab5375..10ea5c1f18 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -333,6 +333,76 @@ The default 'update' hook, when enabled--and with\n `hooks.allowunannotated` config option unset or set to false--prevents\n unannotated tags to be pushed.\n \n+[[proc-receive]]\n+proc-receive\n+~~~~~~~~~~~~\n+This hook is invoked by linkgit:git-receive-pack[1] when it reacts to\n+special `git push` command.  According to refnames of the commands which\n+`git push` sends to 'git-receive-pack', the commands will be devided\n+into two groups by matching what the `receive.procReceiveRefs`\n+configuration variable defines.  One group of the commands will execute\n+the internal `execute_commands` function to update the corresponding\n+refnames, and the other group of commands which have matching refnames\n+will execute this 'proc-receive' hook to create pull requests, etc.\n+If there is no `receive.procReceiveRefs` settings, this hook won't\n+execute at all, and all commands are sent to the internal\n+`execute_commands` function.\n+\n+Its exit status only determines the success or failure of the group of\n+commands with special refnames, unless atomic push is in use.\n+\n+This hook executes once for the receive operation.  It takes no\n+arguments, but will talk a protocol in pkt-line format with the\n+'receive-pack' for reading commands, push-options (optional), and\n+sending result.  In the following example, The letter \"S\" stands for\n+\"receive-pack\" and letter \"H\" stands for the hook.\n+\n+    # Version and capabilities negotiation.\n+    S: PKT-LINE(version=1\\0push-options atomic...)\n+    S: flush-pkt\n+    H: PKT-LINE(version=1\\0push-options...)\n+    H: flush-pkt\n+\n+    # Send commands from server to the hook.\n+    S: PKT-LINE(old-oid new-oid ref)\n+    S: ... ...\n+    S: flush-pkt\n+    # Only if push-options have been negotiated.\n+    S: PKT-LINE(push-option)\n+    S: ... ...\n+    S: flush-pkt\n+\n+    # Receive result from the hook.\n+    # OK, run this command successfully.\n+    H: PKT-LINE(old-oid new-oid ref ok)\n+    # NO, I reject it.\n+    H: PKT-LINE(old-oid new-oid ref ng reason)\n+    # OK, but use an alternate reference.\n+    H: PKT-LINE(old-oid new-oid ref ok ref:alt-ref)\n+    # It will fallthrough to receive-pack to execute.\n+    H: PKT-LINE(old-oid new-oid ref ft)\n+    H: ... ...\n+    H: flush-pkt\n+\n+The \"proc-receive\" hook may update one or more references, and will send\n+its result one by one in pkt-line format.  Each line of the result has\n+four fields and one optional message field, like \"<old-oid> <new-oid>\n+<ref> <status> [<message>]\".\n+\n+The first three fields are the same as those of the commands for\n+\"receive-pack\".\n+\n+The forth field has a two-letter status code.  Available status codes:\n+\n+* ok: The command runs successfully.  If the optional message has a\n+  prefix \"ref:\", the hook has created/updated an alternate reference\n+  instead.\n+\n+* ng: Fail to run the command.  Error message is given in the optional\n+  message field.\n+\n+* ft: Will fallthrough to receive-pack to execute.\n+\n [[post-receive]]\n post-receive\n ~~~~~~~~~~~~\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394674","messageId":"20200403160838.6252-7-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqqftdl3fss.fsf@gitster.c.googlers.com","subject":"[PATCH v7 6/7] t5412: test proc-receive hook on HTTP protocol","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-03T16:08:37Z","receivedAt":"2020-04-03T16:08:56Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nCopy from t5411 to test \"proc-receive\" hook on HTTP protocol.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5412-proc-receive-hook-http-protocol.sh | 944 +++++++++++++++++++++\n 1 file changed, 944 insertions(+)\n create mode 100755 t/t5412-proc-receive-hook-http-protocol.sh\n\ndiff --git a/t/t5412-proc-receive-hook-http-protocol.sh b/t/t5412-proc-receive-hook-http-protocol.sh\nnew file mode 100755\nindex 0000000000..fc3dca9d78\n--- /dev/null\n+++ b/t/t5412-proc-receive-hook-http-protocol.sh\n@@ -0,0 +1,944 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook for HTTP protocol'\n+\n+. ./test-lib.sh\n+\n+ROOT_PATH=\"$PWD\"\n+. \"$TEST_DIRECTORY\"/lib-gpg.sh\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+. \"$TEST_DIRECTORY\"/lib-terminal.sh\n+start_httpd\n+\n+# Create commits in <repo> and assign each commit's oid to shell variables\n+# given in the arguments (A, B, and C). E.g.:\n+#\n+#     create_commits_in <repo> A B C\n+#\n+# NOTE: Never calling this function from a subshell since variable\n+# assignments will disappear when subshell exits.\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{})\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  We can easily prepare the expect text\n+# without having to worry about future changes of the commit ID and spaces\n+# of the output.  Single quotes are replaced with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect txt.  We also\n+# remove some locale error messages, which break test if we turn on\n+# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n+# on plumbing commands.\n+make_user_friendly_and_stable_output () {\n+\tsed \\\n+\t\t-e \"s/  *\\$//\" \\\n+\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/'/\\\"/g\" \\\n+\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n+\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n+\t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n+\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n+\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\" \\\n+\t\t-e \"s#To .*/upstream.git#To <URL/of/upstream.git>#\" \\\n+\t\t-e \"/^error: / d\" \\\n+\t\t-e \"/^remote: fatal: /d\"\n+}\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+test_expect_success \"setup\" '\n+\tgit init --bare upstream &&\n+\tgit -C upstream config http.receivepack true &&\n+\tgit init workbench &&\n+\tcreate_commits_in workbench A B &&\n+\t(\n+\t\tcd workbench &&\n+\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\tgit config core.abbrev 7 &&\n+\t\tgit remote add origin ../upstream &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit tag -m \"v123\" v123 $A &&\n+\t\tgit push origin \\\n+\t\t\t$B:refs/heads/master \\\n+\t\t\t$A:refs/heads/next\n+\t) &&\n+\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t# setup pre-receive hook\n+\tcat >upstream/hooks/pre-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# pre-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"pre-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\t# setup post-receive hook\n+\tcat >upstream/hooks/post-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# post-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"post-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\tchmod a+x \\\n+\t\tupstream/hooks/pre-receive \\\n+\t\tupstream/hooks/post-receive &&\n+\n+\tupstream=\"$HTTPD_DOCUMENT_ROOT_PATH/upstream.git\" &&\n+\tmv upstream \"$upstream\" &&\n+\tgit -C workbench remote set-url origin $HTTPD_URL/smart/upstream.git\n+'\n+\n+setup_askpass_helper\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"normal git-push command\" '\n+\tgit -C workbench push -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tHEAD:refs/heads/master \\\n+\t\tHEAD:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo <URL/of/upstream.git>\n+\t + <OID>...<OID> HEAD -> master (forced update)\n+\t - [deleted] next\n+\t * [new tag] v123 -> v123\n+\t * [new reference] HEAD -> refs/review/master/topic\n+\t * [new branch] HEAD -> a/b/c\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\n+\n+test_expect_success \"add two receive.procReceiveRefs settings\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit config --add receive.procReceiveRefs refs/for/ &&\n+\t\tgit config --add receive.procReceiveRefs refs/review/\n+\t)\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"no proc-receive hook, fail to push special ref\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic:                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"no proc-receive hook, fail all for atomic push\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad version)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive bad protocol: unknown version\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tremote: proc-receive did not exit properly\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive bad protocol: no report\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad oid)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"bad-id new-id ref ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: bad oid\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> bad-id new-id ref ok\n+\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"bad-id new-id ref ok\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: no status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic xx msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: unknown status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic xx msg\n+\tremote: error: proc-receive has bad status \"xx\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic bad status\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: bad status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic bad status\n+\tremote: error: proc-receive has bad status \"bad status\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (no message)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (has message)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng error msg\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (report status on builtin command)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/heads/master ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: warning on report for builtin command\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/heads/master ok\n+\tremote: error: proc-receive reported status on ref of builtin command: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t <OID>..<OID> <COMMIT-B> -> master\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\n+\n+test_expect_success \"setup proc-receive hook (ok)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: no report from proc-receive\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"not support push options\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tfatal: the receiving end does not support push options\n+\tfatal: the remote end hung up unexpectedly\n+\tEOF\n+\ttest_i18ncmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"push with options\" '\n+\tgit -C workbench push \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok ref:refs/pull/123/head\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok ref:refs/pull/124/head\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/review/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"report update of all special refs\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/review/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok ref:refs/pull/123/head\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok ref:refs/pull/124/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/pull/123/head\n+\t * [new reference] HEAD -> refs/review/a/b/c/topic\n+\t * [new reference] HEAD -> refs/pull/124/head\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"report mixed refs update\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID>..<OID> <COMMIT-B> -> master\n+\t * [new branch] HEAD -> bar\n+\t * [new branch] HEAD -> baz\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new branch] HEAD -> foo\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"config receive.procReceiveRefs for all ref/\" '\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs/\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$B $A refs/heads/master ft\" \\\n+\t\t-r \"$A $ZERO_OID refs/heads/foo ft\" \\\n+\t\t-r \"$A $B refs/heads/bar ft\" \\\n+\t\t-r \"$A $B refs/for/master/topic ok ref:refs/pull/123/head\" \\\n+\t\t-r \"$B $A refs/for/next/topic ok ref:refs/pull/124/head\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      :                       (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"report test: fallthrough\" '\n+\tgit -C workbench push -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/heads/master ft\n+\tremote: proc-receive> <COMMIT-A> <ZERO-OID> refs/heads/foo ft\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/heads/bar ft\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/for/master/topic ok ref:refs/pull/123/head\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/next/topic ok ref:refs/pull/124/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/next/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID>..<OID> <COMMIT-B> -> bar\n+\t - [deleted] foo\n+\t + <OID>...<OID> HEAD -> master (forced update)\n+\t * [new reference] HEAD -> refs/pull/123/head\n+\t * [new reference] HEAD -> refs/pull/124/head\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394766","messageId":"20200404134345.10655-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200403160838.6252-1-worldhello.net@gmail.com","subject":"[PATCH v8 0/7] New proc-receive hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-04T13:43:38Z","receivedAt":"2020-04-04T13:43:56Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\n\n## Changes since v7\n\n* Parallel test (make -j7 test) failed because of the failure message on\n  calling `finish_command()` is not stable for test.  Remove the error\n  message in this revision.\n\n    -\tif (finish_command(&proc)) {\n    -\t\tstrbuf_addstr(&errmsg, \"proc-receive did not exit properly\\n\");\n    +\tif (finish_command(&proc))\n     \t\tcode = -1;\n    -\t}\n\n\n## Changes since v6\n\n* Test failed because two message streams of `copy_to_sideband()` and\n  `rp_error()` are mixed together.  Store error messages in `errmsg`\n  and output the errmsg after closing the sideband muxer.\n\n* Use test_i18ncmp instead of mark the whole test case using\n  C_LOCALE_OUTPUT.\n\n* Please merge this topic after topic `jx/atomic-push`.  One test case\n  depends on it.\n\n\n## Range-diff v6...v7\n\n1:  64c9ca7086 = 1:  19c66785d1 transport: not report a non-head push as a branch\n2:  0d27cc5e80 ! 2:  085ded61f5 receive-pack: add new proc-receive hook\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\t    *p++ != ' ' ||\n     +\t\t    parse_oid_hex(p, &new_oid, &p) ||\n     +\t\t    *p++ != ' ') {\n    -+\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\\n\",\n    ++\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\",\n     +\t\t\t\t    reader->line);\n     +\t\t\treturn -1;\n     +\t\t}\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\trefname = p;\n     +\t\tstatus = strchr(p, ' ');\n     +\t\tif (!status) {\n    -+\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\\n\",\n    ++\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\",\n     +\t\t\t\t    reader->line);\n     +\t\t\treturn -1;\n     +\t\t}\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\t\t*msg++ = '\\0';\n     +\t\t}\n     +\t\tif (strlen(status) != 2) {\n    -+\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n    ++\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\",\n     +\t\t\t\t    status, reader->line);\n     +\t\t\treturn -1;\n     +\t\t}\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\t\t\thint->error_string = xstrdup(msg);\n     +\t\t\telse\n     +\t\t\t\thint->error_string = \"failed\";\n    -+\t\t\tcode = 1;\n    ++\t\t\tcode = -1;\n     +\t\t} else if (strcmp(\"ok\", status)) {\n    -+\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n    ++\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\",\n     +\t\t\t\t    status, reader->line);\n     +\t\t\treturn -1;\n     +\t\t}\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\n     +\tfor (cmd = commands; cmd; cmd = cmd->next)\n     +\t\tif (cmd->run_proc_receive &&\n    -+\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED))\n    ++\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {\n     +\t\t    cmd->error_string = \"no report from proc-receive\";\n    ++\t\t    code = -1;\n    ++\t\t}\n     +\n     +\treturn code;\n     +}\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t}\n     +\n     +\tif (version != 1) {\n    -+\t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\\n\",\n    ++\t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n     +\t\t\t    version);\n     +\t\tcode = -1;\n     +\t\tgoto cleanup;\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\tclose(proc.out);\n     +\tif (use_sideband)\n     +\t\tfinish_async(&muxer);\n    -+\tif (finish_command(&proc)) {\n    -+\t\tstrbuf_addstr(&errmsg, \"proc-receive did not exit properly\\n\");\n    ++\tif (finish_command(&proc))\n     +\t\tcode = -1;\n    -+\t}\n     +\tif (errmsg.len >0) {\n     +\t\tchar *p = errmsg.buf;\n     +\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: # proc-receive hook\n     +\tremote: fatal: the remote end hung up unexpectedly\n     +\tremote: error: proc-receive version \"2\" is not supported\n    -+\tremote: proc-receive did not exit properly\n     +\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\tEOF\n3:  81f086188c = 3:  230f28198f refs.c: refactor to reuse ref_is_hidden()\n4:  52675b55a2 ! 4:  e6a7608a84 receive-pack: new config receive.procReceiveRefs\n    @@ t/t5411-proc-receive-hook.sh: test_expect_success \"cleanup\" '\n     +test_expect_success \"add two receive.procReceiveRefs settings\" '\n     +\t(\n     +\t\tcd \"$upstream\" &&\n    -+\t\tgit config --add receive.procReceiveRefs refs/for/ &&\n    ++\t\tgit config --add receive.procReceiveRefs refs/for &&\n     +\t\tgit config --add receive.procReceiveRefs refs/review/\n     +\t)\n     +'\n5:  65876b1e75 ! 5:  e426775925 receive-pack: refactor report for proc-receive\n    @@ builtin/receive-pack.c: static void write_head_info(void)\n     @@ builtin/receive-pack.c: static int read_proc_receive_result(struct packet_reader *reader,\n      \t\t\telse\n      \t\t\t\thint->error_string = \"failed\";\n    - \t\t\tcode = 1;\n    + \t\t\tcode = -1;\n     -\t\t} else if (strcmp(\"ok\", status)) {\n     +\t\t} else if (!strcmp(\"ok\", status)) {\n     +\t\t\thint->extra_string = xstrdup_or_null(msg);\n    @@ builtin/receive-pack.c: static int read_proc_receive_result(struct packet_reader\n     +\t\t\t/* Reset \"run_proc_receive\" field, and continue to run in \"receive-pack\" */\n     +\t\t\thint->run_proc_receive = 0;\n     +\t\t} else {\n    - \t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n    + \t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\",\n      \t\t\t\t    status, reader->line);\n      \t\t\treturn -1;\n     @@ builtin/receive-pack.c: static void report(struct command *commands, const char *unpack_status)\n6:  04ec372c35 ! 6:  c5982067be t5412: test proc-receive hook on HTTP protocol\n    @@ Metadata\n     Author: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n     \n      ## Commit message ##\n    -    t5412: test proc-receive hook on HTTP protocol\n    +    t5412: test the proc-receive hook on HTTP protocol\n     \n    -    Copy from t5411 to test \"proc-receive\" hook on HTTP protocol.\n    +    Copy from t5411 to test the \"proc-receive\" hook on the HTTP protocol.\n     \n         Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n     \n    @@ t/t5412-proc-receive-hook-http-protocol.sh (new)\n     +\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n     +\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\" \\\n     +\t\t-e \"s#To .*/upstream.git#To <URL/of/upstream.git>#\" \\\n    -+\t\t-e \"/^error: / d\" \\\n    -+\t\t-e \"/^remote: fatal: /d\"\n    ++\t\t-e \"/^error: / d\"\n     +}\n     +\n     +# Refs of upstream : master(B)  next(A)\n    @@ t/t5412-proc-receive-hook-http-protocol.sh (new)\n     +test_expect_success \"add two receive.procReceiveRefs settings\" '\n     +\t(\n     +\t\tcd \"$upstream\" &&\n    -+\t\tgit config --add receive.procReceiveRefs refs/for/ &&\n    ++\t\tgit config --add receive.procReceiveRefs refs/for &&\n     +\t\tgit config --add receive.procReceiveRefs refs/review/\n     +\t)\n     +'\n    @@ t/t5412-proc-receive-hook-http-protocol.sh (new)\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    -+\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\t# Apache calls \"receive-pack\" with system locale settings, which\n    ++\t# makes the message \"remote: fatal: ...\" unstable for test.\n    ++\tsed -e \"/^remote: fatal: / d\" out | \\\n    ++\tmake_user_friendly_and_stable_output >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: # pre-receive hook\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: error: proc-receive version \"2\" is not supported\n    -+\tremote: proc-receive did not exit properly\n     +\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n     +\tEOF\n7:  4ab8873f12 = 7:  823b7f2ea6 doc: add documentation for the proc-receive hook\n\n---\n\nJiang Xin (7):\n  transport: not report a non-head push as a branch\n  receive-pack: add new proc-receive hook\n  refs.c: refactor to reuse ref_is_hidden()\n  receive-pack: new config receive.procReceiveRefs\n  receive-pack: refactor report for proc-receive\n  t5412: test the proc-receive hook on HTTP protocol\n  doc: add documentation for the proc-receive hook\n\n Documentation/config/receive.txt           |  14 +\n Documentation/githooks.txt                 |  70 ++\n Makefile                                   |   1 +\n builtin/receive-pack.c                     | 312 ++++++-\n refs.c                                     |  11 +-\n refs.h                                     |   1 +\n t/helper/test-proc-receive.c               | 172 ++++\n t/helper/test-tool.c                       |   1 +\n t/helper/test-tool.h                       |   1 +\n t/t5411-proc-receive-hook.sh               | 931 ++++++++++++++++++++\n t/t5412-proc-receive-hook-http-protocol.sh | 945 +++++++++++++++++++++\n t/t5516-fetch-push.sh                      |   2 +-\n transport-helper.c                         |  64 +-\n transport.c                                |  62 +-\n 14 files changed, 2521 insertions(+), 66 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100755 t/t5412-proc-receive-hook-http-protocol.sh\n\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394767","messageId":"20200404134345.10655-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200403160838.6252-1-worldhello.net@gmail.com","subject":"[PATCH v8 1/7] transport: not report a non-head push as a branch","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-04T13:43:39Z","receivedAt":"2020-04-04T13:43:57Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing a new reference (not a head or tag), report it as a new\nreference instead of a new branch.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411-proc-receive-hook.sh | 153 +++++++++++++++++++++++++++++++++++\n t/t5516-fetch-push.sh        |   2 +-\n transport.c                  |   9 ++-\n 3 files changed, 160 insertions(+), 4 deletions(-)\n create mode 100755 t/t5411-proc-receive-hook.sh\n\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nnew file mode 100755\nindex 0000000000..1784bcb584\n--- /dev/null\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -0,0 +1,153 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook'\n+\n+. ./test-lib.sh\n+\n+# Create commits in <repo> and assign each commit's oid to shell variables\n+# given in the arguments (A, B, and C). E.g.:\n+#\n+#     create_commits_in <repo> A B C\n+#\n+# NOTE: Never calling this function from a subshell since variable\n+# assignments will disappear when subshell exits.\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{})\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  We can easily prepare the expect text\n+# without having to worry about future changes of the commit ID and spaces\n+# of the output.  We also replce single quotes with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect txt.\n+make_user_friendly_and_stable_output () {\n+\tsed \\\n+\t\t-e \"s/  *\\$//\" \\\n+\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/'/\\\"/g\" \\\n+\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n+\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n+\t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n+\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n+\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\"\n+}\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+test_expect_success \"setup\" '\n+\tgit init --bare upstream &&\n+\tgit init workbench &&\n+\tcreate_commits_in workbench A B &&\n+\t(\n+\t\tcd workbench &&\n+\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\tgit config core.abbrev 7 &&\n+\t\tgit remote add origin ../upstream &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit tag -m \"v123\" v123 $A &&\n+\t\tgit push origin \\\n+\t\t\t$B:refs/heads/master \\\n+\t\t\t$A:refs/heads/next\n+\t) &&\n+\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t# setup pre-receive hook\n+\tcat >upstream/hooks/pre-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# pre-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"pre-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\t# setup post-receive hook\n+\tcat >upstream/hooks/post-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# post-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"post-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\tchmod a+x \\\n+\t\tupstream/hooks/pre-receive \\\n+\t\tupstream/hooks/post-receive\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"normal git-push command\" '\n+\tgit -C workbench push -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tHEAD:refs/heads/master \\\n+\t\tHEAD:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo ../upstream\n+\t + <OID>...<OID> HEAD -> master (forced update)\n+\t - [deleted] next\n+\t * [new tag] v123 -> v123\n+\t * [new reference] HEAD -> refs/review/master/topic\n+\t * [new branch] HEAD -> a/b/c\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C upstream show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 9ff041a093..9e4b9313b5 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1039,7 +1039,7 @@ test_force_fetch_tag \"annotated tag\" \"-f -a -m'tag message'\"\n test_expect_success 'push --porcelain' '\n \tmk_empty testrepo &&\n \techo >.git/foo  \"To testrepo\" &&\n-\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new branch]\"  &&\n+\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new reference]\"  &&\n \techo >>.git/foo \"Done\" &&\n \tgit push >.git/bar --porcelain  testrepo refs/heads/master:refs/remotes/origin/master &&\n \t(\ndiff --git a/transport.c b/transport.c\nindex 75c5c9fe98..4f91c8ea17 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -500,9 +500,12 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\t\t\t porcelain, summary_width);\n \telse if (is_null_oid(&ref->old_oid))\n \t\tprint_ref_status('*',\n-\t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n-\t\t\t\"[new branch]\"),\n-\t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t  ? \"[new tag]\"\n+\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t     ? \"[new branch]\"\n+\t\t\t\t     : \"[new reference]\")),\n+\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394768","messageId":"20200404134345.10655-4-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200403160838.6252-1-worldhello.net@gmail.com","subject":"[PATCH v8 3/7] refs.c: refactor to reuse ref_is_hidden()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-04T13:43:41Z","receivedAt":"2020-04-04T13:43:57Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd new function `ref_is_matched()` to reuse `ref_is_hidden()`. Will use\nthis function for `receive-pack` to check commands with specific\nprefixes.\n\nTest case t5512 covered this change.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n refs.c | 11 ++++++++---\n refs.h |  1 +\n 2 files changed, 9 insertions(+), 3 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 1ab0bb54d3..229159ea1a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1389,13 +1389,18 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti\n }\n \n int ref_is_hidden(const char *refname, const char *refname_full)\n+{\n+\treturn ref_is_matched(hide_refs, refname, refname_full);\n+}\n+\n+int ref_is_matched(struct string_list *match_refs, const char *refname, const char *refname_full)\n {\n \tint i;\n \n-\tif (!hide_refs)\n+\tif (!match_refs)\n \t\treturn 0;\n-\tfor (i = hide_refs->nr - 1; i >= 0; i--) {\n-\t\tconst char *match = hide_refs->items[i].string;\n+\tfor (i = match_refs->nr - 1; i >= 0; i--) {\n+\t\tconst char *match = match_refs->items[i].string;\n \t\tconst char *subject;\n \t\tint neg = 0;\n \t\tconst char *p;\ndiff --git a/refs.h b/refs.h\nindex 545029c6d8..a2ea043f7f 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -739,6 +739,7 @@ int parse_hide_refs_config(const char *var, const char *value, const char *);\n  * parameter always points to the full ref name.\n  */\n int ref_is_hidden(const char *, const char *);\n+int ref_is_matched(struct string_list *, const char *, const char *);\n \n enum ref_type {\n \tREF_TYPE_PER_WORKTREE,\t  /* refs inside refs/ but not shared       */\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394769","messageId":"20200404134345.10655-5-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200403160838.6252-1-worldhello.net@gmail.com","subject":"[PATCH v8 4/7] receive-pack: new config receive.procReceiveRefs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-04T13:43:42Z","receivedAt":"2020-04-04T13:44:00Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd a new multi-valued config variable \"receive.procReceiveRefs\"\nfor `receive-pack` command, like the follows:\n\n    git config --system --add receive.procReceiveRefs refs/for/\n    git config --system --add receive.procReceiveRefs refs/drafts/\n\nIf the specific prefix strings match the reference names of the commands\nwhich are sent by git client to `receive-pack`, these commands will be\nexecuted by an external hook (named \"proc-receive\"), instead of the\ninternal `execute_commands` function.\n\nFor example, if it is set to \"refs/for/\", pushing to a reference such as\n\"refs/for/master\" will not create or update reference \"refs/for/master\",\nbut may create or update a pull request directly by running the external\nhook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/config/receive.txt |  14 ++++\n builtin/receive-pack.c           |  43 ++++++++--\n t/t5411-proc-receive-hook.sh     | 130 +++++++++++++++++++++++++++++++\n 3 files changed, 180 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/receive.txt b/Documentation/config/receive.txt\nindex 65f78aac37..0178f2d478 100644\n--- a/Documentation/config/receive.txt\n+++ b/Documentation/config/receive.txt\n@@ -114,6 +114,20 @@ receive.hideRefs::\n \tAn attempt to update or delete a hidden ref by `git push` is\n \trejected.\n \n+receive.procReceiveRefs::\n+\tThis is a multi-valued variable that defines reference prefixes\n+\tto match the commands in `receive-pack`.  Commands matching the\n+\tprefixes will be executed by an external hooks \"proc-receive\",\n+\tinstead of the internal `execute_commands` function.  If this\n+\tvariable is not defined, the \"proc-receive\" hook will never be\n+\tused, and all commands will be executed by the internal\n+\t`execute_commands` function.\n+\n+\tFor example, if this variable is set to \"refs/for/\", pushing to\n+\treference such as \"refs/for/master\" will not create or update a\n+\treference named \"refs/for/master\", but may create or update a\n+\tpull request directly by running an external hook.\n+\n receive.updateServerInfo::\n \tIf set to true, git-receive-pack will run git-update-server-info\n \tafter receiving data from git-push and updating refs.\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 0760536d58..26e561d5b8 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -76,6 +76,7 @@ static struct object_id push_cert_oid;\n static struct signature_check sigcheck;\n static const char *push_cert_nonce;\n static const char *cert_nonce_seed;\n+static struct string_list proc_receive_refs;\n \n static const char *NONCE_UNSOLICITED = \"UNSOLICITED\";\n static const char *NONCE_BAD = \"BAD\";\n@@ -228,6 +229,20 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (strcmp(var, \"receive.procreceiverefs\") == 0) {\n+\t\tchar *prefix;\n+\t\tint len;\n+\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tprefix = xstrdup(value);\n+\t\tlen = strlen(prefix);\n+\t\twhile (len && prefix[len - 1] == '/')\n+\t\t\tprefix[--len] = '\\0';\n+\t\tstring_list_insert(&proc_receive_refs, prefix);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -1732,15 +1747,26 @@ static void execute_commands(struct command *commands,\n \t/* Try to find commands that have special prefix in their reference names,\n \t * and mark them to run an external \"proc-receive\" hook later.\n \t */\n-\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n-\t\t\tcontinue;\n+\tif (proc_receive_refs.nr > 0) {\n+\t\tstruct strbuf refname_full = STRBUF_INIT;\n+\t\tsize_t prefix_len;\n+\n+\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n+\t\tprefix_len = refname_full.len;\n \n-\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n-\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n-\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n-\t\t\trun_proc_receive = 1;\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tif (!should_process_cmd(cmd))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_setlen(&refname_full, prefix_len);\n+\t\t\tstrbuf_addstr(&refname_full, cmd->ref_name);\n+\t\t\tif (ref_is_matched(&proc_receive_refs, cmd->ref_name, refname_full.buf)) {\n+\t\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\t\trun_proc_receive = 1;\n+\t\t\t}\n \t\t}\n+\n+\t\tstrbuf_release(&refname_full);\n \t}\n \n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n@@ -2200,6 +2226,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n+\tstring_list_init(&proc_receive_refs, 0);\n+\n \tpacket_trace_identity(\"receive-pack\");\n \n \targc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);\n@@ -2315,5 +2343,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \toid_array_clear(&shallow);\n \toid_array_clear(&ref);\n \tfree((void *)push_cert_nonce);\n+\tstring_list_clear(&proc_receive_refs, 0);\n \treturn 0;\n }\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex dbcf9f165c..b04a7ec814 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -167,6 +167,14 @@ test_expect_success \"cleanup\" '\n \t)\n '\n \n+test_expect_success \"add two receive.procReceiveRefs settings\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit config --add receive.procReceiveRefs refs/for &&\n+\t\tgit config --add receive.procReceiveRefs refs/review/\n+\t)\n+'\n+\n # Refs of upstream : master(A)\n # Refs of workbench: master(A)  tags/v123\n # git push         :                       next(A)  refs/for/master/topic(A)\n@@ -720,4 +728,126 @@ test_expect_success \"push with options\" '\n \ttest_cmp expect actual\n '\n \n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/review/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"report update of all special refs\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/review/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/review/a/b/c/topic\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"report mixed refs update\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> bar\n+\t * [new branch] HEAD -> baz\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new branch] HEAD -> foo\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394770","messageId":"20200404134345.10655-8-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200403160838.6252-1-worldhello.net@gmail.com","subject":"[PATCH v8 7/7] doc: add documentation for the proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-04T13:43:45Z","receivedAt":"2020-04-04T13:44:01Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd documentation for the new \"proc-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/githooks.txt | 70 ++++++++++++++++++++++++++++++++++++++\n 1 file changed, 70 insertions(+)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 3dccab5375..10ea5c1f18 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -333,6 +333,76 @@ The default 'update' hook, when enabled--and with\n `hooks.allowunannotated` config option unset or set to false--prevents\n unannotated tags to be pushed.\n \n+[[proc-receive]]\n+proc-receive\n+~~~~~~~~~~~~\n+This hook is invoked by linkgit:git-receive-pack[1] when it reacts to\n+special `git push` command.  According to refnames of the commands which\n+`git push` sends to 'git-receive-pack', the commands will be devided\n+into two groups by matching what the `receive.procReceiveRefs`\n+configuration variable defines.  One group of the commands will execute\n+the internal `execute_commands` function to update the corresponding\n+refnames, and the other group of commands which have matching refnames\n+will execute this 'proc-receive' hook to create pull requests, etc.\n+If there is no `receive.procReceiveRefs` settings, this hook won't\n+execute at all, and all commands are sent to the internal\n+`execute_commands` function.\n+\n+Its exit status only determines the success or failure of the group of\n+commands with special refnames, unless atomic push is in use.\n+\n+This hook executes once for the receive operation.  It takes no\n+arguments, but will talk a protocol in pkt-line format with the\n+'receive-pack' for reading commands, push-options (optional), and\n+sending result.  In the following example, The letter \"S\" stands for\n+\"receive-pack\" and letter \"H\" stands for the hook.\n+\n+    # Version and capabilities negotiation.\n+    S: PKT-LINE(version=1\\0push-options atomic...)\n+    S: flush-pkt\n+    H: PKT-LINE(version=1\\0push-options...)\n+    H: flush-pkt\n+\n+    # Send commands from server to the hook.\n+    S: PKT-LINE(old-oid new-oid ref)\n+    S: ... ...\n+    S: flush-pkt\n+    # Only if push-options have been negotiated.\n+    S: PKT-LINE(push-option)\n+    S: ... ...\n+    S: flush-pkt\n+\n+    # Receive result from the hook.\n+    # OK, run this command successfully.\n+    H: PKT-LINE(old-oid new-oid ref ok)\n+    # NO, I reject it.\n+    H: PKT-LINE(old-oid new-oid ref ng reason)\n+    # OK, but use an alternate reference.\n+    H: PKT-LINE(old-oid new-oid ref ok ref:alt-ref)\n+    # It will fallthrough to receive-pack to execute.\n+    H: PKT-LINE(old-oid new-oid ref ft)\n+    H: ... ...\n+    H: flush-pkt\n+\n+The \"proc-receive\" hook may update one or more references, and will send\n+its result one by one in pkt-line format.  Each line of the result has\n+four fields and one optional message field, like \"<old-oid> <new-oid>\n+<ref> <status> [<message>]\".\n+\n+The first three fields are the same as those of the commands for\n+\"receive-pack\".\n+\n+The forth field has a two-letter status code.  Available status codes:\n+\n+* ok: The command runs successfully.  If the optional message has a\n+  prefix \"ref:\", the hook has created/updated an alternate reference\n+  instead.\n+\n+* ng: Fail to run the command.  Error message is given in the optional\n+  message field.\n+\n+* ft: Will fallthrough to receive-pack to execute.\n+\n [[post-receive]]\n post-receive\n ~~~~~~~~~~~~\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394771","messageId":"20200404134345.10655-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200403160838.6252-1-worldhello.net@gmail.com","subject":"[PATCH v8 2/7] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-04T13:43:40Z","receivedAt":"2020-04-04T13:44:01Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nGit calls an internal `execute_commands` function to handle commands\nsent from client to `git-receive-pack`.  Regardless of what references\nthe user pushes, git creates or updates the corresponding references if\nthe user has write-permission.  A contributor who has no\nwrite-permission, cannot push to the repository directly.  So, the\ncontributor has to write commits to an alternate location, and sends\npull request by emails or by other ways.  We call this workflow as a\ndistributed workflow.\n\nIt would be more convenient to work in a centralized workflow like what\nGerrit provided for some cases.  For example, a read-only user who\ncannot push to a branch directly can run the following `git push`\ncommand to push commits to a pseudo reference (has a prefix \"refs/for/\",\nnot \"refs/heads/\") to create a code review.\n\n    git push origin \\\n        HEAD:refs/for/<branch-name>/<session>\n\nThe `<branch-name>` in the above example can be as simple as \"master\",\nor a more complicated branch name like \"foo/bar\".  The `<session>` in\nthe above example command can be the local branch name of the client\nside, such as \"my/topic\".\n\nWe cannot implement a centralized workflow elegantly by using\n\"pre-receive\" + \"post-receive\", because Git will call the internal\nfunction \"execute_commands\" to create references (even the special\npseudo reference) between these two hooks.  Even though we can delete\nthe temporarily created pseudo reference via the \"post-receive\" hook,\nhaving a temporary reference is not safe for concurrent pushes.\n\nSo, add a filter and a new handler to support this kind of workflow.\nThe filter will check the prefix of the reference name, and if the\ncommand has a special reference name, the filter will turn a specific\nfield (`run_proc_receive`) on for the command.  Commands with this filed\nturned on will be executed by a new handler (an hook named\n\"proc-receive\") instead of the internal `execute_commands` function.\nWe can use this \"proc-receive\" command to create pull requests or send\nemails for code review.\n\nSuggested by Junio, this \"proc-receive\" hook reads the commands,\npush-options (optional), and send result using a protocol in pkt-line\nformat.  In the following example, The letter \"S\" stands for\n\"receive-pack\" and letter \"H\" stands for the hook.\n\n    # Version and capabilities negotiation.\n    S: PKT-LINE(version=1\\0push-options atomic...)\n    S: flush-pkt\n    H: PKT-LINE(version=1\\0push-options...)\n    H: flush-pkt\n\n    # Send commands from server to the hook.\n    S: PKT-LINE(old-oid new-oid ref)\n    S: ... ...\n    S: flush-pkt\n    # Only if push-options have been negotiated.\n    S: PKT-LINE(push-option)\n    S: ... ...\n    S: flush-pkt\n\n    # Receive result from the hook.\n    # OK, run this command successfully.\n    H: PKT-LINE(old-oid new-oid ref ok)\n    # NO, I reject it.\n    H: PKT-LINE(old-oid new-oid ref ng reason)\n    # OK, but use an alternate reference. (in latter commit)\n    H: PKT-LINE(old-oid new-oid ref ok ref:alt-ref)\n    # It will fallthrough to receive-pack to execute. (in latter commit)\n    H: PKT-LINE(old-oid new-oid ref ft)\n    H: ... ...\n    H: flush-pkt\n\nAfter receiving a command, the hook can create/update another alternate\nreference.  For example, a command for a reference \"refs/for/master\" may\ncreate a special reference, such as \"refs/pull/123/head\".  The alternate\nreference can be returned from the result in an extensible format like\n\"<old-oid> <new-oid> <reference> <status> [<message>]\".\n\nThe result will be stored in a command list, and \"receive-pack\" will use\nthe result to replace the commands that have specific `run_proc_receive`\nfield turned on.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Makefile                     |   1 +\n builtin/receive-pack.c       | 264 +++++++++++++++-\n t/helper/test-proc-receive.c | 172 ++++++++++\n t/helper/test-tool.c         |   1 +\n t/helper/test-tool.h         |   1 +\n t/t5411-proc-receive-hook.sh | 592 ++++++++++++++++++++++++++++++++++-\n 6 files changed, 1017 insertions(+), 14 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n\ndiff --git a/Makefile b/Makefile\nindex ef1ff2228f..1c52c280ce 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -725,6 +725,7 @@ TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\n+TEST_BUILTINS_OBJS += test-proc-receive.o\n TEST_BUILTINS_OBJS += test-progress.o\n TEST_BUILTINS_OBJS += test-reach.o\n TEST_BUILTINS_OBJS += test-read-cache.o\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 2cc18bbffd..0760536d58 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -308,11 +308,14 @@ static void write_head_info(void)\n \tpacket_flush(1);\n }\n \n+#define RUN_PROC_RECEIVE_SCHEDULED\t1\n+#define RUN_PROC_RECEIVE_RETURNED\t2\n struct command {\n \tstruct command *next;\n \tconst char *error_string;\n \tunsigned int skip_update:1,\n-\t\t     did_not_exist:1;\n+\t\t     did_not_exist:1,\n+\t\t     run_proc_receive:2;\n \tint index;\n \tstruct object_id old_oid;\n \tstruct object_id new_oid;\n@@ -817,6 +820,234 @@ static int run_update_hook(struct command *cmd)\n \treturn finish_command(&proc);\n }\n \n+static struct command *find_command_by_refname(const struct command *list,\n+\t\t\t\t\t       const char *refname)\n+{\n+\tfor ( ; list; list = list->next)\n+\t\tif (!strcmp(list->ref_name, refname))\n+\t\t\treturn (struct command *)list;\n+\treturn NULL;\n+}\n+\n+static int read_proc_receive_result(struct packet_reader *reader,\n+\t\t\t\t    struct command *commands,\n+\t\t\t\t    struct strbuf *errmsg)\n+{\n+\tstruct command *hint;\n+\tstruct command *cmd;\n+\tint code = 0;\n+\n+\thint = NULL;\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\t\tchar *status;\n+\t\tchar *msg = NULL;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ') {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\",\n+\t\t\t\t    reader->line);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\trefname = p;\n+\t\tstatus = strchr(p, ' ');\n+\t\tif (!status) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\",\n+\t\t\t\t    reader->line);\n+\t\t\treturn -1;\n+\t\t}\n+\t\t*status++ = '\\0';\n+\t\tif (strlen(status) > 2 && *(status + 2) == ' ') {\n+\t\t\tmsg = status + 2;\n+\t\t\t*msg++ = '\\0';\n+\t\t}\n+\t\tif (strlen(status) != 2) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\",\n+\t\t\t\t    status, reader->line);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\t/* first try searching at our hint, falling back to all refs */\n+\t\tif (hint)\n+\t\t\thint = find_command_by_refname(hint, refname);\n+\t\tif (!hint)\n+\t\t\thint = find_command_by_refname(commands, refname);\n+\t\tif (!hint) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unknown ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!hint->run_proc_receive) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on ref of builtin command: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcontinue;\n+\t\t}\n+\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n+\t\toidcpy(&hint->old_oid, &old_oid);\n+\t\toidcpy(&hint->new_oid, &new_oid);\n+\t\tif (!strcmp(status, \"ng\")) {\n+\t\t\tif (msg)\n+\t\t\t\thint->error_string = xstrdup(msg);\n+\t\t\telse\n+\t\t\t\thint->error_string = \"failed\";\n+\t\t\tcode = -1;\n+\t\t} else if (strcmp(\"ok\", status)) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\",\n+\t\t\t\t    status, reader->line);\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+\n+\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\tif (cmd->run_proc_receive &&\n+\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {\n+\t\t    cmd->error_string = \"no report from proc-receive\";\n+\t\t    code = -1;\n+\t\t}\n+\n+\treturn code;\n+}\n+\n+static int run_proc_receive_hook(struct command *commands,\n+\t\t\t\t const struct string_list *push_options)\n+{\n+\tstruct child_process proc = CHILD_PROCESS_INIT;\n+\tstruct async muxer;\n+\tstruct command *cmd;\n+\tconst char *argv[2];\n+\tstruct packet_reader reader;\n+\tstruct strbuf cap = STRBUF_INIT;\n+\tstruct strbuf errmsg = STRBUF_INIT;\n+\tint pr_use_push_options = 0;\n+\tint version = 0;\n+\tint code;\n+\n+\targv[0] = find_hook(\"proc-receive\");\n+\tif (!argv[0]) {\n+\t\trp_error(\"cannot find hook 'proc-receive'\");\n+\t\treturn -1;\n+\t}\n+\targv[1] = NULL;\n+\n+\tproc.argv = argv;\n+\tproc.in = -1;\n+\tproc.out = -1;\n+\tproc.trace2_hook_name = \"proc-receive\";\n+\n+\tif (use_sideband) {\n+\t\tmemset(&muxer, 0, sizeof(muxer));\n+\t\tmuxer.proc = copy_to_sideband;\n+\t\tmuxer.in = -1;\n+\t\tcode = start_async(&muxer);\n+\t\tif (code)\n+\t\t\treturn code;\n+\t\tproc.err = muxer.in;\n+\t} else {\n+\t\tproc.err = 0;\n+\t}\n+\n+\tcode = start_command(&proc);\n+\tif (code) {\n+\t\tif (use_sideband)\n+\t\t\tfinish_async(&muxer);\n+\t\treturn code;\n+\t}\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\n+\t/* Version negotiaton */\n+\tpacket_reader_init(&reader, proc.out, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\tif (use_atomic)\n+\t\tstrbuf_addstr(&cap, \" atomic\");\n+\tif (use_push_options)\n+\t\tstrbuf_addstr(&cap, \" push-options\");\n+\tif (cap.len) {\n+\t\tpacket_write_fmt(proc.in, \"version=1%c%s\\n\", '\\0', cap.buf + 1);\n+\t\tstrbuf_release(&cap);\n+\t} else {\n+\t\tpacket_write_fmt(proc.in, \"version=1\\n\");\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader.pktlen > 8 && starts_with(reader.line, \"version=\")) {\n+\t\t\tversion = atoi(reader.line + 8);\n+\t\t\tlinelen = strlen(reader.line);\n+\t\t\tif (linelen < reader.pktlen) {\n+\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tpr_use_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (version != 1) {\n+\t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n+\t\t\t    version);\n+\t\tcode = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\t/* Send commands */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tchar *old_hex, *new_hex;\n+\n+\t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)\n+\t\t\tcontinue;\n+\t\told_hex = oid_to_hex(&cmd->old_oid);\n+\t\tnew_hex = oid_to_hex(&cmd->new_oid);\n+\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n+\t\t\t\t old_hex, new_hex, cmd->ref_name);\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\t/* Send push options */\n+\tif (pr_use_push_options) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tfor_each_string_list_item(item, push_options)\n+\t\t\tpacket_write_fmt(proc.in, \"%s\", item->string);\n+\t\tpacket_flush(proc.in);\n+\t}\n+\n+\t/* Read result from proc-receive */\n+\tcode = read_proc_receive_result(&reader, commands, &errmsg);\n+\n+cleanup:\n+\tclose(proc.in);\n+\tclose(proc.out);\n+\tif (use_sideband)\n+\t\tfinish_async(&muxer);\n+\tif (finish_command(&proc))\n+\t\tcode = -1;\n+\tif (errmsg.len >0) {\n+\t\tchar *p = errmsg.buf;\n+\n+\t\tp += errmsg.len - 1;\n+\t\tif (*p == '\\n')\n+\t\t\t*p = '\\0';\n+\t\trp_error(\"%s\", errmsg.buf);\n+\t\tstrbuf_release(&errmsg);\n+\t}\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn code;\n+}\n+\n static char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n@@ -1392,7 +1623,7 @@ static void execute_commands_non_atomic(struct command *commands,\n \tstruct strbuf err = STRBUF_INIT;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\ttransaction = ref_transaction_begin(&err);\n@@ -1432,7 +1663,7 @@ static void execute_commands_atomic(struct command *commands,\n \t}\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\tcmd->error_string = update(cmd, si);\n@@ -1468,6 +1699,7 @@ static void execute_commands(struct command *commands,\n \tstruct iterate_data data;\n \tstruct async muxer;\n \tint err_fd = 0;\n+\tint run_proc_receive = 0;\n \n \tif (unpacker_error) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n@@ -1497,6 +1729,20 @@ static void execute_commands(struct command *commands,\n \n \treject_updates_to_hidden(commands);\n \n+\t/* Try to find commands that have special prefix in their reference names,\n+\t * and mark them to run an external \"proc-receive\" hook later.\n+\t */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!should_process_cmd(cmd))\n+\t\t\tcontinue;\n+\n+\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n+\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n+\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\trun_proc_receive = 1;\n+\t\t}\n+\t}\n+\n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n \t\t\tif (!cmd->error_string)\n@@ -1523,6 +1769,18 @@ static void execute_commands(struct command *commands,\n \tfree(head_name_to_free);\n \thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, NULL, NULL);\n \n+\tif (run_proc_receive) {\n+\t\tint code;\n+\n+\t\tcode = run_proc_receive_hook(commands, push_options);\n+\t\tif (code) {\n+\t\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\t\tif (!cmd->error_string  && (cmd->run_proc_receive || use_atomic))\n+\t\t\t\t\tcmd->error_string = \"fail to run proc-receive hook\";\n+\t\t\t}\n+\t\t}\n+\t}\n+\n \tif (use_atomic)\n \t\texecute_commands_atomic(commands, si);\n \telse\ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nnew file mode 100644\nindex 0000000000..ed30817905\n--- /dev/null\n+++ b/t/helper/test-proc-receive.c\n@@ -0,0 +1,172 @@\n+#include \"cache.h\"\n+#include \"connect.h\"\n+#include \"parse-options.h\"\n+#include \"pkt-line.h\"\n+#include \"string-list.h\"\n+#include \"test-tool.h\"\n+\n+static const char *proc_receive_usage[] = {\n+\t\"test-tool proc-receive [<options>...]\",\n+\tNULL\n+};\n+\n+static int version = 1;\n+static int verbose = 0;\n+static int no_push_options = 0;\n+static int use_atomic = 0;\n+static int use_push_options = 0;\n+static struct string_list returns = STRING_LIST_INIT_NODUP;\n+\n+struct command {\n+\tstruct command *next;\n+\tconst char *error_string;\n+\tunsigned int skip_update:1,\n+\t\t     did_not_exist:1;\n+\tint index;\n+\tstruct object_id old_oid;\n+\tstruct object_id new_oid;\n+\tchar ref_name[FLEX_ARRAY]; /* more */\n+};\n+\n+static void proc_receive_verison(struct packet_reader *reader) {\n+\tint server_version = 0;\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader->pktlen > 8 && starts_with(reader->line, \"version=\")) {\n+\t\t\tserver_version = atoi(reader->line+8);\n+\t\t\tlinelen = strlen(reader->line);\n+\t\t\tif (linelen < reader->pktlen) {\n+\t\t\t\tconst char *feature_list = reader->line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"atomic\"))\n+\t\t\t\t\tuse_atomic= 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tuse_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (server_version != 1)\n+\t\tdie(\"bad protocol version: %d\", server_version);\n+\n+\tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n+\t\t\t version, '\\0',\n+\t\t\t use_push_options && !no_push_options ? \"push-options\": \"\");\n+\tpacket_flush(1);\n+}\n+\n+static void proc_receive_read_commands(struct packet_reader *reader,\n+\t\t\t\t       struct command **commands)\n+{\n+\tstruct command **tail = commands;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tstruct command *cmd;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ')\n+\t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n+\t\t\t    reader->line);\n+\t\trefname = p;\n+\t\tFLEX_ALLOC_STR(cmd, ref_name, refname);\n+\t\toidcpy(&cmd->old_oid, &old_oid);\n+\t\toidcpy(&cmd->new_oid, &new_oid);\n+\n+\t\t*tail = cmd;\n+\t\ttail = &cmd->next;\n+\t}\n+}\n+\n+static void proc_receive_read_push_options(struct packet_reader *reader,\n+\t\t\t\t\t   struct string_list *options)\n+{\n+\n+\tif (no_push_options || !use_push_options)\n+\t       return;\n+\n+\twhile (1) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tstring_list_append(options, reader->line);\n+\t}\n+}\n+\n+int cmd__proc_receive(int argc, const char **argv)\n+{\n+\tstruct packet_reader reader;\n+\tstruct command *commands;\n+\tstruct string_list push_options = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"no-push-options\", &no_push_options,\n+\t\t\t \"disable push options\"),\n+\t\tOPT_STRING_LIST('r', \"return\", &returns, \"old/new/ref/status/msg\",\n+\t\t\t\t\"return of results\"),\n+\t\tOPT__VERBOSE(&verbose, \"be verbose\"),\n+\t\tOPT_INTEGER('V', \"version\", &version,\n+\t\t\t    \"use this protocol version number\"),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, \"test-tools\", options, proc_receive_usage, 0);\n+\tif (argc > 0)\n+\t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n+\n+\tpacket_reader_init(&reader, 0, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tproc_receive_verison(&reader);\n+\tproc_receive_read_commands(&reader, &commands);\n+\tproc_receive_read_push_options(&reader, &push_options);\n+\n+\tif (verbose) {\n+\t\tstruct command *cmd;\n+\n+\t\tif (use_push_options || use_atomic)\n+\t\t\tfprintf(stderr, \"proc-receive:%s%s\\n\",\n+\t\t\t\tuse_atomic? \" atomic\": \"\",\n+\t\t\t\tuse_push_options ? \" push_options\": \"\");\n+\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tchar *old_hex, *new_hex;\n+\n+\t\t\told_hex = oid_to_hex(&cmd->old_oid);\n+\t\t\tnew_hex = oid_to_hex(&cmd->new_oid);\n+\t\t\tfprintf(stderr, \"proc-receive< %s %s %s\\n\",\n+\t\t\t\told_hex, new_hex, cmd->ref_name);\n+\t\t}\n+\n+\t\tif (push_options.nr > 0) {\n+\t\t\tfor_each_string_list_item(item, &push_options)\n+\t\t\t\tfprintf(stderr, \"proc-receive< %s\\n\", item->string);\n+\t\t}\n+\n+\t\tif (returns.nr) {\n+\t\t\tfor_each_string_list_item(item, &returns)\n+\t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n+\t\t}\n+\t}\n+\n+\tif (returns.nr) {\n+\t\tfor_each_string_list_item(item, &returns)\n+\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n+\t}\n+\tpacket_flush(1);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 31eedcd241..f865433a61 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -44,6 +44,7 @@ static struct test_cmd cmds[] = {\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\n+\t{ \"proc-receive\", cmd__proc_receive},\n \t{ \"progress\", cmd__progress },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 4eb5e6609e..059008cf22 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -34,6 +34,7 @@ int cmd__parse_pathspec_file(int argc, const char** argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\n+int cmd__proc_receive(int argc, const char **argv);\n int cmd__progress(int argc, const char **argv);\n int cmd__reach(int argc, const char **argv);\n int cmd__read_cache(int argc, const char **argv);\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex 1784bcb584..dbcf9f165c 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -43,8 +43,11 @@ create_commits_in () {\n # Format the output of git-push, git-show-ref and other commands to make a\n # user-friendly and stable text.  We can easily prepare the expect text\n # without having to worry about future changes of the commit ID and spaces\n-# of the output.  We also replce single quotes with double quotes, because\n-# it is boring to prepare unquoted single quotes in expect txt.\n+# of the output.  Single quotes are replaced with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect txt.  We also\n+# remove some locale error messages, which break test if we turn on\n+# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n+# on plumbing commands.\n make_user_friendly_and_stable_output () {\n \tsed \\\n \t\t-e \"s/  *\\$//\" \\\n@@ -54,13 +57,16 @@ make_user_friendly_and_stable_output () {\n \t\t-e \"s/$B/<COMMIT-B>/g\" \\\n \t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n \t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n-\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\"\n+\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\" \\\n+\t\t-e \"s#To ../upstream.git#To <URL/of/upstream.git>#\" \\\n+\t\t-e \"/^error: / d\"\n }\n \n # Refs of upstream : master(B)  next(A)\n # Refs of workbench: master(A)           tags/v123\n test_expect_success \"setup\" '\n-\tgit init --bare upstream &&\n+\tupstream=upstream.git &&\n+\tgit init --bare \"$upstream\" &&\n \tgit init workbench &&\n \tcreate_commits_in workbench A B &&\n \t(\n@@ -68,7 +74,7 @@ test_expect_success \"setup\" '\n \t\t# Try to make a stable fixed width for abbreviated commit ID,\n \t\t# this fixed-width oid will be replaced with \"<OID>\".\n \t\tgit config core.abbrev 7 &&\n-\t\tgit remote add origin ../upstream &&\n+\t\tgit remote add origin ../$upstream &&\n \t\tgit update-ref refs/heads/master $A &&\n \t\tgit tag -m \"v123\" v123 $A &&\n \t\tgit push origin \\\n@@ -78,7 +84,7 @@ test_expect_success \"setup\" '\n \tTAG=$(git -C workbench rev-parse v123) &&\n \n \t# setup pre-receive hook\n-\tcat >upstream/hooks/pre-receive <<-\\EOF &&\n+\tcat >\"$upstream/hooks/pre-receive\" <<-\\EOF &&\n \t#!/bin/sh\n \n \techo >&2 \"# pre-receive hook\"\n@@ -90,7 +96,7 @@ test_expect_success \"setup\" '\n \tEOF\n \n \t# setup post-receive hook\n-\tcat >upstream/hooks/post-receive <<-\\EOF &&\n+\tcat >\"$upstream/hooks/post-receive\" <<-\\EOF &&\n \t#!/bin/sh\n \n \techo >&2 \"# post-receive hook\"\n@@ -102,8 +108,8 @@ test_expect_success \"setup\" '\n \tEOF\n \n \tchmod a+x \\\n-\t\tupstream/hooks/pre-receive \\\n-\t\tupstream/hooks/post-receive\n+\t\t\"$upstream/hooks/pre-receive\" \\\n+\t\t\"$upstream/hooks/post-receive\"\n '\n \n # Refs of upstream : master(B)  next(A)\n@@ -131,7 +137,7 @@ test_expect_success \"normal git-push command\" '\n \tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n-\tTo ../upstream\n+\tTo <URL/of/upstream.git>\n \t + <OID>...<OID> HEAD -> master (forced update)\n \t - [deleted] next\n \t * [new tag] v123 -> v123\n@@ -139,7 +145,7 @@ test_expect_success \"normal git-push command\" '\n \t * [new branch] HEAD -> a/b/c\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C upstream show-ref >out &&\n+\tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n \t<COMMIT-A> refs/heads/a/b/c\n@@ -150,4 +156,568 @@ test_expect_success \"normal git-push command\" '\n \ttest_cmp expect actual\n '\n \n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"no proc-receive hook, fail to push special ref\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic:                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"no proc-receive hook, fail all for atomic push\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad version)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive bad protocol: unknown version\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: fatal: the remote end hung up unexpectedly\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_i18ncmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive bad protocol: no report\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad oid)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"bad-id new-id ref ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: bad oid\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> bad-id new-id ref ok\n+\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"bad-id new-id ref ok\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: no status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic xx msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: unknown status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic xx msg\n+\tremote: error: proc-receive has bad status \"xx\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic bad status\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: bad status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic bad status\n+\tremote: error: proc-receive has bad status \"bad status\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (no message)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (has message)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng error msg\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (report status on builtin command)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/heads/master ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: warning on report for builtin command\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/heads/master ok\n+\tremote: error: proc-receive reported status on ref of builtin command: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t <OID>..<OID> <COMMIT-B> -> master\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\n+\n+test_expect_success \"setup proc-receive hook (ok)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: no report from proc-receive\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"not support push options\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tfatal: the receiving end does not support push options\n+\tfatal: the remote end hung up unexpectedly\n+\tEOF\n+\ttest_i18ncmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"push with options\" '\n+\tgit -C workbench push \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394772","messageId":"20200404134345.10655-6-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200403160838.6252-1-worldhello.net@gmail.com","subject":"[PATCH v8 5/7] receive-pack: refactor report for proc-receive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-04T13:43:43Z","receivedAt":"2020-04-04T13:44:01Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThe \"proc-receive\" may update one or more references, and will send its\nresult one by one in pkt-line format.  Each line of the result has four\nfields and one optional message field, as \"<old-oid> <new-oid> <ref>\n<status> [<message>]\".  See the following example:\n\n    # OK, run this command successfully.\n    PKT-LINE(old-oid new-oid ref ok)\n\n    # NO, I reject it.\n    PKT-LINE(old-oid new-oid ref ng reason)\n\n    # OK, but use an alternate reference.\n    PKT-LINE(old-oid new-oid ref ok ref:alt-ref)\n\n    # It will fallthrough to receive-pack to execute.\n    PKT-LINE(old-oid new-oid ref ft)\n\nThe first three fields have the same foramt as a command.\n\nThe forth field has a two-letter status code.  Available status code:\n\n* ok: The command runs successfully.  If the optional message has a\n  prefix \"ref:\", the hook has created/updated an alternate reference\n  instead.\n\n* ng: Fail to run the command. Error message is in the optional message\n  field.\n\n* ft: Will fallthrough to receive-pack to execute.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c       | 21 +++++++--\n t/t5411-proc-receive-hook.sh | 90 +++++++++++++++++++++++++++++++++---\n transport-helper.c           | 64 ++++++++++++-------------\n transport.c                  | 59 +++++++++++++----------\n 4 files changed, 168 insertions(+), 66 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 26e561d5b8..3843ae0fe9 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -328,6 +328,7 @@ static void write_head_info(void)\n struct command {\n \tstruct command *next;\n \tconst char *error_string;\n+\tconst char *extra_string;\n \tunsigned int skip_update:1,\n \t\t     did_not_exist:1,\n \t\t     run_proc_receive:2;\n@@ -913,7 +914,12 @@ static int read_proc_receive_result(struct packet_reader *reader,\n \t\t\telse\n \t\t\t\thint->error_string = \"failed\";\n \t\t\tcode = -1;\n-\t\t} else if (strcmp(\"ok\", status)) {\n+\t\t} else if (!strcmp(\"ok\", status)) {\n+\t\t\thint->extra_string = xstrdup_or_null(msg);\n+\t\t} else if (!strcmp(\"ft\", status)) {\n+\t\t\t/* Reset \"run_proc_receive\" field, and continue to run in \"receive-pack\" */\n+\t\t\thint->run_proc_receive = 0;\n+\t\t} else {\n \t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\",\n \t\t\t\t    status, reader->line);\n \t\t\treturn -1;\n@@ -2183,12 +2189,17 @@ static void report(struct command *commands, const char *unpack_status)\n \tpacket_buf_write(&buf, \"unpack %s\\n\",\n \t\t\t unpack_status ? unpack_status : \"ok\");\n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!cmd->error_string)\n-\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n-\t\t\t\t\t cmd->ref_name);\n-\t\telse\n+\t\tif (!cmd->error_string) {\n+\t\t\tif (!cmd->extra_string)\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n+\t\t\t\t\t\t cmd->ref_name);\n+\t\t\telse\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s%c%s\\n\",\n+\t\t\t\t\t\t cmd->ref_name, ' ', cmd->extra_string);\n+\t\t} else {\n \t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n \t\t\t\t\t cmd->ref_name, cmd->error_string);\n+\t\t}\n \t}\n \tpacket_buf_flush(&buf);\n \ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex b04a7ec814..6104641759 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -742,8 +742,8 @@ test_expect_success \"setup proc-receive hook\" '\n \n \ttest-tool proc-receive -v \\\n \t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n-\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n-\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok ref:refs/pull/123/head\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok ref:refs/pull/124/head\"\n \tEOF\n \tchmod a+x \"$upstream/hooks/proc-receive\"\n '\n@@ -768,16 +768,16 @@ test_expect_success \"report update of all special refs\" '\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n-\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n-\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok ref:refs/pull/123/head\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok ref:refs/pull/124/head\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \t * [new reference] HEAD -> refs/review/a/b/c/topic\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/pull/124/head\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -806,6 +806,7 @@ test_expect_success \"setup proc-receive hook\" '\n # git push         :                       bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n test_expect_success \"report mixed refs update\" '\n \tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n \t\tHEAD:refs/heads/bar \\\n \t\tHEAD:refs/heads/baz \\\n \t\tHEAD:refs/for/next/topic \\\n@@ -815,6 +816,7 @@ test_expect_success \"report mixed refs update\" '\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n@@ -826,12 +828,14 @@ test_expect_success \"report mixed refs update\" '\n \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n \tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n+\t <OID>..<OID> <COMMIT-B> -> master\n \t * [new branch] HEAD -> bar\n \t * [new branch] HEAD -> baz\n \t * [new reference] HEAD -> refs/for/next/topic\n@@ -845,6 +849,80 @@ test_expect_success \"report mixed refs update\" '\n \t<COMMIT-A> refs/heads/bar\n \t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"config receive.procReceiveRefs for all ref/\" '\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs/\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$B $A refs/heads/master ft\" \\\n+\t\t-r \"$A $ZERO_OID refs/heads/foo ft\" \\\n+\t\t-r \"$A $B refs/heads/bar ft\" \\\n+\t\t-r \"$A $B refs/for/master/topic ok ref:refs/pull/123/head\" \\\n+\t\t-r \"$B $A refs/for/next/topic ok ref:refs/pull/124/head\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      :                       (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"report test: fallthrough\" '\n+\tgit -C workbench push -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/heads/master ft\n+\tremote: proc-receive> <COMMIT-A> <ZERO-OID> refs/heads/foo ft\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/heads/bar ft\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/for/master/topic ok ref:refs/pull/123/head\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/next/topic ok ref:refs/pull/124/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/next/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID>..<OID> <COMMIT-B> -> bar\n+\t - [deleted] foo\n+\t + <OID>...<OID> HEAD -> master (forced update)\n+\t * [new reference] HEAD -> refs/pull/123/head\n+\t * [new reference] HEAD -> refs/pull/124/head\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/master\n \tEOF\n \ttest_cmp expect actual\ndiff --git a/transport-helper.c b/transport-helper.c\nindex a46afcb69d..0c835c5495 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -747,37 +747,39 @@ static int push_update_ref_status(struct strbuf *buf,\n \t\t\tmsg = xstrdup(msg);\n \t\tstrbuf_release(&msg_buf);\n \n-\t\tif (!strcmp(msg, \"no match\")) {\n-\t\t\tstatus = REF_STATUS_NONE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"up to date\")) {\n-\t\t\tstatus = REF_STATUS_UPTODATE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"non-fast forward\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_NONFASTFORWARD;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"already exists\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_ALREADY_EXISTS;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"fetch first\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_FETCH_FIRST;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"needs force\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_NEEDS_FORCE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"stale info\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_STALE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"forced update\")) {\n-\t\t\tforced = 1;\n-\t\t\tFREE_AND_NULL(msg);\n+\t\tif (status != REF_STATUS_OK) {\n+\t\t\tif (!strcmp(msg, \"no match\")) {\n+\t\t\t\tstatus = REF_STATUS_NONE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"up to date\")) {\n+\t\t\t\tstatus = REF_STATUS_UPTODATE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"non-fast forward\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_NONFASTFORWARD;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"already exists\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_ALREADY_EXISTS;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"fetch first\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_FETCH_FIRST;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"needs force\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_NEEDS_FORCE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"stale info\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_STALE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"forced update\")) {\n+\t\t\t\tforced = 1;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n \t\t}\n \t}\n \ndiff --git a/transport.c b/transport.c\nindex 4f91c8ea17..ee9b2a0d8e 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -459,15 +459,18 @@ void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int v\n \t}\n }\n \n-static void print_ref_status(char flag, const char *summary,\n+static void print_ref_status(char flag, const char *summary, char *target_refname,\n \t\t\t     struct ref *to, struct ref *from, const char *msg,\n \t\t\t     int porcelain, int summary_width)\n {\n+\tif (!target_refname)\n+\t\ttarget_refname = to->name;\n+\n \tif (porcelain) {\n \t\tif (from)\n-\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, target_refname);\n \t\telse\n-\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, target_refname);\n \t\tif (msg)\n \t\t\tfprintf(stdout, \"%s (%s)\\n\", summary, msg);\n \t\telse\n@@ -481,9 +484,9 @@ static void print_ref_status(char flag, const char *summary,\n \t\tfprintf(stderr, \" %s%c %-*s%s \", red, flag, summary_width,\n \t\t\tsummary, reset);\n \t\tif (from)\n-\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(to->name));\n+\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(target_refname));\n \t\telse\n-\t\t\tfputs(prettify_refname(to->name), stderr);\n+\t\t\tfputs(prettify_refname(target_refname), stderr);\n \t\tif (msg) {\n \t\t\tfputs(\" (\", stderr);\n \t\t\tfputs(msg, stderr);\n@@ -495,18 +498,26 @@ static void print_ref_status(char flag, const char *summary,\n \n static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_width)\n {\n+\tchar *refname;\n+\n+\tif (ref->remote_status && !strncmp(ref->remote_status, \"ref:\", 4))\n+\t\trefname = ref->remote_status + 4;\n+\telse\n+\t\trefname = ref->name;\n+\n \tif (ref->deletion)\n-\t\tprint_ref_status('-', \"[deleted]\", ref, NULL, NULL,\n+\t\tprint_ref_status('-', \"[deleted]\", refname, ref, NULL, NULL,\n \t\t\t\t porcelain, summary_width);\n-\telse if (is_null_oid(&ref->old_oid))\n+\telse if (is_null_oid(&ref->old_oid)) {\n+\n \t\tprint_ref_status('*',\n-\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t (starts_with(refname, \"refs/tags/\")\n \t\t\t\t  ? \"[new tag]\"\n-\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t  : (starts_with(refname, \"refs/heads/\")\n \t\t\t\t     ? \"[new branch]\"\n \t\t\t\t     : \"[new reference]\")),\n-\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n-\telse {\n+\t\t\t\t refname, ref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t} else {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n \t\tconst char *msg;\n@@ -525,7 +536,7 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\tstrbuf_add_unique_abbrev(&quickref, &ref->new_oid,\n \t\t\t\t\t DEFAULT_ABBREV);\n \n-\t\tprint_ref_status(type, quickref.buf, ref, ref->peer_ref, msg,\n+\t\tprint_ref_status(type, quickref.buf, refname, ref, ref->peer_ref, msg,\n \t\t\t\t porcelain, summary_width);\n \t\tstrbuf_release(&quickref);\n \t}\n@@ -542,56 +553,56 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count,\n \n \tswitch(ref->status) {\n \tcase REF_STATUS_NONE:\n-\t\tprint_ref_status('X', \"[no match]\", ref, NULL, NULL,\n+\t\tprint_ref_status('X', \"[no match]\", NULL, ref, NULL, NULL,\n \t\t\t\t porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NODELETE:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, NULL,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, NULL,\n \t\t\t\t \"remote does not support deleting refs\",\n \t\t\t\t porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_UPTODATE:\n-\t\tprint_ref_status('=', \"[up to date]\", ref,\n+\t\tprint_ref_status('=', \"[up to date]\", NULL, ref,\n \t\t\t\t ref->peer_ref, NULL, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NONFASTFORWARD:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"non-fast-forward\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"already exists\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_FETCH_FIRST:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"fetch first\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NEEDS_FORCE:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"needs force\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_STALE:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"stale info\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_SHALLOW:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"new shallow roots not allowed\",\n \t\t\t\t porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REMOTE_REJECT:\n-\t\tprint_ref_status('!', \"[remote rejected]\", ref,\n+\t\tprint_ref_status('!', \"[remote rejected]\", NULL, ref,\n \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n \t\t\t\t ref->remote_status, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_EXPECTING_REPORT:\n-\t\tprint_ref_status('!', \"[remote failure]\", ref,\n+\t\tprint_ref_status('!', \"[remote failure]\", NULL, ref,\n \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n \t\t\t\t \"remote failed to report status\",\n \t\t\t\t porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_ATOMIC_PUSH_FAILED:\n-\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n \t\t\t\t \"atomic push failed\", porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_OK:\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394773","messageId":"20200404134345.10655-7-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200403160838.6252-1-worldhello.net@gmail.com","subject":"[PATCH v8 6/7] t5412: test the proc-receive hook on HTTP protocol","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-04T13:43:44Z","receivedAt":"2020-04-04T13:44:01Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nCopy from t5411 to test the \"proc-receive\" hook on the HTTP protocol.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5412-proc-receive-hook-http-protocol.sh | 945 +++++++++++++++++++++\n 1 file changed, 945 insertions(+)\n create mode 100755 t/t5412-proc-receive-hook-http-protocol.sh\n\ndiff --git a/t/t5412-proc-receive-hook-http-protocol.sh b/t/t5412-proc-receive-hook-http-protocol.sh\nnew file mode 100755\nindex 0000000000..b16056e024\n--- /dev/null\n+++ b/t/t5412-proc-receive-hook-http-protocol.sh\n@@ -0,0 +1,945 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook for HTTP protocol'\n+\n+. ./test-lib.sh\n+\n+ROOT_PATH=\"$PWD\"\n+. \"$TEST_DIRECTORY\"/lib-gpg.sh\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+. \"$TEST_DIRECTORY\"/lib-terminal.sh\n+start_httpd\n+\n+# Create commits in <repo> and assign each commit's oid to shell variables\n+# given in the arguments (A, B, and C). E.g.:\n+#\n+#     create_commits_in <repo> A B C\n+#\n+# NOTE: Never calling this function from a subshell since variable\n+# assignments will disappear when subshell exits.\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{})\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  We can easily prepare the expect text\n+# without having to worry about future changes of the commit ID and spaces\n+# of the output.  Single quotes are replaced with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect txt.  We also\n+# remove some locale error messages, which break test if we turn on\n+# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n+# on plumbing commands.\n+make_user_friendly_and_stable_output () {\n+\tsed \\\n+\t\t-e \"s/  *\\$//\" \\\n+\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/'/\\\"/g\" \\\n+\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n+\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n+\t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n+\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n+\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\" \\\n+\t\t-e \"s#To .*/upstream.git#To <URL/of/upstream.git>#\" \\\n+\t\t-e \"/^error: / d\"\n+}\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+test_expect_success \"setup\" '\n+\tgit init --bare upstream &&\n+\tgit -C upstream config http.receivepack true &&\n+\tgit init workbench &&\n+\tcreate_commits_in workbench A B &&\n+\t(\n+\t\tcd workbench &&\n+\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\tgit config core.abbrev 7 &&\n+\t\tgit remote add origin ../upstream &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit tag -m \"v123\" v123 $A &&\n+\t\tgit push origin \\\n+\t\t\t$B:refs/heads/master \\\n+\t\t\t$A:refs/heads/next\n+\t) &&\n+\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t# setup pre-receive hook\n+\tcat >upstream/hooks/pre-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# pre-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"pre-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\t# setup post-receive hook\n+\tcat >upstream/hooks/post-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# post-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"post-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\tchmod a+x \\\n+\t\tupstream/hooks/pre-receive \\\n+\t\tupstream/hooks/post-receive &&\n+\n+\tupstream=\"$HTTPD_DOCUMENT_ROOT_PATH/upstream.git\" &&\n+\tmv upstream \"$upstream\" &&\n+\tgit -C workbench remote set-url origin $HTTPD_URL/smart/upstream.git\n+'\n+\n+setup_askpass_helper\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"normal git-push command\" '\n+\tgit -C workbench push -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tHEAD:refs/heads/master \\\n+\t\tHEAD:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo <URL/of/upstream.git>\n+\t + <OID>...<OID> HEAD -> master (forced update)\n+\t - [deleted] next\n+\t * [new tag] v123 -> v123\n+\t * [new reference] HEAD -> refs/review/master/topic\n+\t * [new branch] HEAD -> a/b/c\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\n+\n+test_expect_success \"add two receive.procReceiveRefs settings\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit config --add receive.procReceiveRefs refs/for &&\n+\t\tgit config --add receive.procReceiveRefs refs/review/\n+\t)\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"no proc-receive hook, fail to push special ref\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic:                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"no proc-receive hook, fail all for atomic push\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad version)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive bad protocol: unknown version\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\t# Apache calls \"receive-pack\" with system locale settings, which\n+\t# makes the message \"remote: fatal: ...\" unstable for test.\n+\tsed -e \"/^remote: fatal: / d\" out | \\\n+\tmake_user_friendly_and_stable_output >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive bad protocol: no report\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad oid)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"bad-id new-id ref ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: bad oid\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> bad-id new-id ref ok\n+\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"bad-id new-id ref ok\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: no status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic xx msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: unknown status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic xx msg\n+\tremote: error: proc-receive has bad status \"xx\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic bad status\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: bad status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic bad status\n+\tremote: error: proc-receive has bad status \"bad status\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (no message)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (has message)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng error msg\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (report status on builtin command)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/heads/master ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: warning on report for builtin command\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/heads/master ok\n+\tremote: error: proc-receive reported status on ref of builtin command: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t <OID>..<OID> <COMMIT-B> -> master\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\n+\n+test_expect_success \"setup proc-receive hook (ok)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: no report from proc-receive\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"not support push options\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tfatal: the receiving end does not support push options\n+\tfatal: the remote end hung up unexpectedly\n+\tEOF\n+\ttest_i18ncmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"push with options\" '\n+\tgit -C workbench push \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok ref:refs/pull/123/head\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok ref:refs/pull/124/head\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/review/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"report update of all special refs\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/review/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok ref:refs/pull/123/head\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok ref:refs/pull/124/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/pull/123/head\n+\t * [new reference] HEAD -> refs/review/a/b/c/topic\n+\t * [new reference] HEAD -> refs/pull/124/head\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"report mixed refs update\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID>..<OID> <COMMIT-B> -> master\n+\t * [new branch] HEAD -> bar\n+\t * [new branch] HEAD -> baz\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new branch] HEAD -> foo\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"config receive.procReceiveRefs for all ref/\" '\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs/\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$B $A refs/heads/master ft\" \\\n+\t\t-r \"$A $ZERO_OID refs/heads/foo ft\" \\\n+\t\t-r \"$A $B refs/heads/bar ft\" \\\n+\t\t-r \"$A $B refs/for/master/topic ok ref:refs/pull/123/head\" \\\n+\t\t-r \"$B $A refs/for/next/topic ok ref:refs/pull/124/head\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      :                       (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"report test: fallthrough\" '\n+\tgit -C workbench push -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/heads/master ft\n+\tremote: proc-receive> <COMMIT-A> <ZERO-OID> refs/heads/foo ft\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/heads/bar ft\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/for/master/topic ok ref:refs/pull/123/head\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/next/topic ok ref:refs/pull/124/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/next/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID>..<OID> <COMMIT-B> -> bar\n+\t - [deleted] foo\n+\t + <OID>...<OID> HEAD -> master (forced update)\n+\t * [new reference] HEAD -> refs/pull/123/head\n+\t * [new reference] HEAD -> refs/pull/124/head\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394912","messageId":"20200407120813.25025-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200404134345.10655-1-worldhello.net@gmail.com","subject":"[PATCH v9 0/6] New proc-receive hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-07T12:08:07Z","receivedAt":"2020-04-07T12:08:22Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\n## Changes since v8\n\n* One test case failed in CI for `pu` branch (see:\n  https://github.com/git/git/runs/565992235) with the following error:\n\n        --- expect\t2020-04-06 23:41:27.552286900 +0000\n        +++ actual\t2020-04-06 23:41:27.458511100 +0000\n        @@ -3,6 +3,7 @@\n         remote: # proc-receive hook\n         remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n         remote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic\n        +remote: fatal: unable to write flush packet: Broken pipe\n         remote: error: proc-receive expected \"old new ref status [msg]\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n         To <URL/of/upstream.git>\n          ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n        error: last command exited with $?=1\n        not ok 16 - proc-receive bad protocol: no status\n\n  This can be reproduced with the following changes on \"t/helper/test-proc-receive.c\":\n\n        +++ b/t/helper/test-proc-receive.c\n        @@ -2,6 +2,7 @@\n         #include \"connect.h\"\n         #include \"parse-options.h\"\n         #include \"pkt-line.h\"\n        +#include \"sigchain.h\"\n         #include \"string-list.h\"\n         #include \"test-tool.h\"\n         \n        @@ -130,6 +131,7 @@ int cmd__proc_receive(int argc, const char **argv)\n                                   PACKET_READ_CHOMP_NEWLINE |\n                                   PACKET_READ_DIE_ON_ERR_PACKET);\n         \n        +       sigchain_push(SIGPIPE, SIG_IGN);\n                proc_receive_verison(&reader);\n                proc_receive_read_commands(&reader, &commands);\n                proc_receive_read_push_options(&reader, &push_options);\n        @@ -163,10 +165,14 @@ int cmd__proc_receive(int argc, const char **argv)\n                }\n         \n                if (returns.nr) {\n        -               for_each_string_list_item(item, &returns)\n        +               for_each_string_list_item(item, &returns) {\n                                packet_write_fmt(1, \"%s\\n\", item->string);\n        +                       sleep(1);\n        +               }\n                }\n                packet_flush(1);\n        +       sigchain_pop(SIGPIPE);\n         \n                return 0;\n         }\n\n  \"test-proc-receive\" send results one by one to \"receive-pack\" and send a \n  flush-pkt to end the communication.  But \"receive-pack\" will close the pipe,\n  if a syntax error is found in the result sent from \"test-proc-receive\".\n  The closed pipe makes \"test-proc-receive\" complain.\n\n  Make modifications in patch 2/6 of this reroll, so we don't have to adjust\n  test cases. E.g.,\n  \n         /* receive-pack */\n         static int read_proc_receive_result(...)\n         {\n             ... ...\n             for (;;) {\n                 ... ...\n         \n                 if (packet_reader_read(reader) != PACKET_READ_NORMAL)\n                         break;\n                 if (parse_oid_hex(reader->line, &old_oid, &p) ||\n                     *p++ != ' ' ||\n                     parse_oid_hex(p, &new_oid, &p) ||\n                     *p++ != ' ') {\n                         strbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\\n\",\n                                     reader->line);\n        -                return -1;\n        +                code = -1;\n        +                continue;\n                 }\n\n\n* Patch 1/6: Add `t/t5411/common-functions.sh` and `t/t5411/common-test-cases.sh` to\n  reuse test cases between t5411 and t5412.\n\n* Refactor for a smaller patch 5/6 (receive-pack: refactor report for proc-receive).\n\n\n## Range-diff v8...v9\n\n1:  19c66785d1 ! 1:  ba6222899b transport: not report a non-head push as a branch\n\n    t/t5411-proc-receive-hook.sh               | 75 ++++++++++++++++++++++++++\n    t/t5411/common-functions.sh                | 49 +++++++++++++++++\n    t/t5411/common-test-cases.sh               | 43 +++++++++++++++\n    t/t5412-proc-receive-hook-http-protocol.sh | 86 ++++++++++++++++++++++++++++++\n\n2:  085ded61f5 ! 2:  195c5b0a0c receive-pack: add new proc-receive hook\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\t    *p++ != ' ' ||\n     +\t\t    parse_oid_hex(p, &new_oid, &p) ||\n     +\t\t    *p++ != ' ') {\n    -+\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\",\n    ++\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\\n\",\n     +\t\t\t\t    reader->line);\n    -+\t\t\treturn -1;\n    ++\t\t\tcode = -1;\n    ++\t\t\tcontinue;\n     +\t\t}\n     +\n     +\t\trefname = p;\n     +\t\tstatus = strchr(p, ' ');\n     +\t\tif (!status) {\n    -+\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\",\n    ++\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\\n\",\n     +\t\t\t\t    reader->line);\n    -+\t\t\treturn -1;\n    ++\t\t\tcode = -1;\n    ++\t\t\tcontinue;\n     +\t\t}\n     +\t\t*status++ = '\\0';\n     +\t\tif (strlen(status) > 2 && *(status + 2) == ' ') {\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\t\t*msg++ = '\\0';\n     +\t\t}\n     +\t\tif (strlen(status) != 2) {\n    -+\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\",\n    ++\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n     +\t\t\t\t    status, reader->line);\n    -+\t\t\treturn -1;\n    ++\t\t\tcode = -1;\n    ++\t\t\tcontinue;\n     +\t\t}\n     +\n     +\t\t/* first try searching at our hint, falling back to all refs */\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\t\t\t    refname);\n     +\t\t\tcontinue;\n     +\t\t}\n    -+\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n    -+\t\toidcpy(&hint->old_oid, &old_oid);\n    -+\t\toidcpy(&hint->new_oid, &new_oid);\n     +\t\tif (!strcmp(status, \"ng\")) {\n     +\t\t\tif (msg)\n     +\t\t\t\thint->error_string = xstrdup(msg);\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\t\t\thint->error_string = \"failed\";\n     +\t\t\tcode = -1;\n     +\t\t} else if (strcmp(\"ok\", status)) {\n    -+\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\",\n    ++\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n     +\t\t\t\t    status, reader->line);\n    -+\t\t\treturn -1;\n    ++\t\t\tcode = -1;\n    ++\t\t\t/* Skip marking it as RUN_PROC_RECEIVE_RETURNED */\n    ++\t\t\tcontinue;\n     +\t\t}\n    ++\t\toidcpy(&hint->old_oid, &old_oid);\n    ++\t\toidcpy(&hint->new_oid, &new_oid);\n    ++\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n     +\t}\n     +\n     +\tfor (cmd = commands; cmd; cmd = cmd->next)\n    @@ t/helper/test-proc-receive.c (new)\n     +#include \"connect.h\"\n     +#include \"parse-options.h\"\n     +#include \"pkt-line.h\"\n    ++#include \"sigchain.h\"\n     +#include \"string-list.h\"\n     +#include \"test-tool.h\"\n     +\n    @@ t/helper/test-proc-receive.c (new)\n     +\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n     +\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n     +\n    ++\tsigchain_push(SIGPIPE, SIG_IGN);\n     +\tproc_receive_verison(&reader);\n     +\tproc_receive_read_commands(&reader, &commands);\n     +\tproc_receive_read_push_options(&reader, &push_options);\n    @@ t/helper/test-proc-receive.c (new)\n     +\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n     +\t}\n     +\tpacket_flush(1);\n    ++\tsigchain_pop(SIGPIPE);\n     +\n     +\treturn 0;\n     +}\n\n3:  230f28198f = 3:  cde556e9c7 refs.c: refactor to reuse ref_is_hidden()\n\n4:  e6a7608a84 ! 4:  3200327695 receive-pack: new config receive.procReceiveRefs\n    @@ Documentation/config/receive.txt: receive.hideRefs::\n     +\tused, and all commands will be executed by the internal\n     +\t`execute_commands` function.\n     +\n    -+\tFor example, if this variable is set to \"refs/for/\", pushing to\n    ++\tFor example, if this variable is set to \"refs/for\", pushing to\n     +\treference such as \"refs/for/master\" will not create or update a\n     +\treference named \"refs/for/master\", but may create or update a\n     +\tpull request directly by running an external hook.\n     \n5:  e426775925 ! 5:  5f2ab02b01 receive-pack: refactor report for proc-receive\n    @@ builtin/receive-pack.c: static int read_proc_receive_result(struct packet_reader\n     +\t\t\t/* Reset \"run_proc_receive\" field, and continue to run in \"receive-pack\" */\n     +\t\t\thint->run_proc_receive = 0;\n     +\t\t} else {\n    - \t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\",\n    + \t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n      \t\t\t\t    status, reader->line);\n    - \t\t\treturn -1;\n    + \t\t\tcode = -1;\n    @@ builtin/receive-pack.c: static int read_proc_receive_result(struct packet_reader *reader,\n    + \t\t}\n    + \t\toidcpy(&hint->old_oid, &old_oid);\n    + \t\toidcpy(&hint->new_oid, &new_oid);\n    +-\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n    ++\t\tif (hint->run_proc_receive)\n    ++\t\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n    + \t}\n    + \n    + \tfor (cmd = commands; cmd; cmd = cmd->next)\n    @@ transport.c: void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int v\n    - \t}\n    - }\n    - \n    --static void print_ref_status(char flag, const char *summary,\n    -+static void print_ref_status(char flag, const char *summary, char *target_refname,\n    +@@ transport.c: static void print_ref_status(char flag, const char *summary,\n      \t\t\t     struct ref *to, struct ref *from, const char *msg,\n      \t\t\t     int porcelain, int summary_width)\n      {\n    -+\tif (!target_refname)\n    -+\t\ttarget_refname = to->name;\n    ++\tchar *from_name = NULL;\n    ++\tchar *to_name = NULL;\n    ++\n    ++\tif (from) {\n    ++\t\tif (from->remote_status && !strncmp(from->remote_status, \"ref:\", 4))\n    ++\t\t\tfrom_name = from->remote_status + 4;\n    ++\t\telse\n    ++\t\t\tfrom_name = from->name;\n    ++\t}\n    ++\n    ++\tif (to) {\n    ++\t\tif (to->remote_status && !strncmp(to->remote_status, \"ref:\", 4))\n    ++\t\t\tto_name = to->remote_status + 4;\n    ++\t\telse\n    ++\t\t\tto_name = to->name;\n    ++\t}\n     +\n      \tif (porcelain) {\n      \t\tif (from)\n     -\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to->name);\n    -+\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, target_refname);\n    ++\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from_name, to_name);\n      \t\telse\n     -\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to->name);\n    -+\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, target_refname);\n    ++\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to_name);\n      \t\tif (msg)\n      \t\t\tfprintf(stdout, \"%s (%s)\\n\", summary, msg);\n      \t\telse\n    @@ transport.c: static void print_ref_status(char flag, const char *summary,\n      \t\t\tsummary, reset);\n      \t\tif (from)\n     -\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(to->name));\n    -+\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(target_refname));\n    ++\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from_name), prettify_refname(to_name));\n      \t\telse\n     -\t\t\tfputs(prettify_refname(to->name), stderr);\n    -+\t\t\tfputs(prettify_refname(target_refname), stderr);\n    ++\t\t\tfputs(prettify_refname(to_name), stderr);\n      \t\tif (msg) {\n      \t\t\tfputs(\" (\", stderr);\n      \t\t\tfputs(msg, stderr);\n    -@@ transport.c: static void print_ref_status(char flag, const char *summary,\n    - \n    - static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_width)\n    - {\n    -+\tchar *refname;\n    -+\n    -+\tif (ref->remote_status && !strncmp(ref->remote_status, \"ref:\", 4))\n    -+\t\trefname = ref->remote_status + 4;\n    -+\telse\n    -+\t\trefname = ref->name;\n    -+\n    - \tif (ref->deletion)\n    --\t\tprint_ref_status('-', \"[deleted]\", ref, NULL, NULL,\n    -+\t\tprint_ref_status('-', \"[deleted]\", refname, ref, NULL, NULL,\n    - \t\t\t\t porcelain, summary_width);\n    --\telse if (is_null_oid(&ref->old_oid))\n    -+\telse if (is_null_oid(&ref->old_oid)) {\n    -+\n    - \t\tprint_ref_status('*',\n    --\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n    -+\t\t\t\t (starts_with(refname, \"refs/tags/\")\n    - \t\t\t\t  ? \"[new tag]\"\n    --\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n    -+\t\t\t\t  : (starts_with(refname, \"refs/heads/\")\n    - \t\t\t\t     ? \"[new branch]\"\n    - \t\t\t\t     : \"[new reference]\")),\n    --\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n    --\telse {\n    -+\t\t\t\t refname, ref, ref->peer_ref, NULL, porcelain, summary_width);\n    -+\t} else {\n    - \t\tstruct strbuf quickref = STRBUF_INIT;\n    - \t\tchar type;\n    - \t\tconst char *msg;\n    -@@ transport.c: static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n    - \t\tstrbuf_add_unique_abbrev(&quickref, &ref->new_oid,\n    - \t\t\t\t\t DEFAULT_ABBREV);\n    - \n    --\t\tprint_ref_status(type, quickref.buf, ref, ref->peer_ref, msg,\n    -+\t\tprint_ref_status(type, quickref.buf, refname, ref, ref->peer_ref, msg,\n    - \t\t\t\t porcelain, summary_width);\n    - \t\tstrbuf_release(&quickref);\n    - \t}\n    -@@ transport.c: static int print_one_push_status(struct ref *ref, const char *dest, int count,\n    - \n    - \tswitch(ref->status) {\n    - \tcase REF_STATUS_NONE:\n    --\t\tprint_ref_status('X', \"[no match]\", ref, NULL, NULL,\n    -+\t\tprint_ref_status('X', \"[no match]\", NULL, ref, NULL, NULL,\n    - \t\t\t\t porcelain, summary_width);\n    - \t\tbreak;\n    - \tcase REF_STATUS_REJECT_NODELETE:\n    --\t\tprint_ref_status('!', \"[rejected]\", ref, NULL,\n    -+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, NULL,\n    - \t\t\t\t \"remote does not support deleting refs\",\n    - \t\t\t\t porcelain, summary_width);\n    - \t\tbreak;\n    - \tcase REF_STATUS_UPTODATE:\n    --\t\tprint_ref_status('=', \"[up to date]\", ref,\n    -+\t\tprint_ref_status('=', \"[up to date]\", NULL, ref,\n    - \t\t\t\t ref->peer_ref, NULL, porcelain, summary_width);\n    - \t\tbreak;\n    - \tcase REF_STATUS_REJECT_NONFASTFORWARD:\n    --\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n    -+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n    - \t\t\t\t \"non-fast-forward\", porcelain, summary_width);\n    - \t\tbreak;\n    - \tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n    --\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n    -+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n    - \t\t\t\t \"already exists\", porcelain, summary_width);\n    - \t\tbreak;\n    - \tcase REF_STATUS_REJECT_FETCH_FIRST:\n    --\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n    -+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n    - \t\t\t\t \"fetch first\", porcelain, summary_width);\n    - \t\tbreak;\n    - \tcase REF_STATUS_REJECT_NEEDS_FORCE:\n    --\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n    -+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n    - \t\t\t\t \"needs force\", porcelain, summary_width);\n    - \t\tbreak;\n    - \tcase REF_STATUS_REJECT_STALE:\n    --\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n    -+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n    - \t\t\t\t \"stale info\", porcelain, summary_width);\n    - \t\tbreak;\n    - \tcase REF_STATUS_REJECT_SHALLOW:\n    --\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n    -+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n    - \t\t\t\t \"new shallow roots not allowed\",\n    - \t\t\t\t porcelain, summary_width);\n    - \t\tbreak;\n    - \tcase REF_STATUS_REMOTE_REJECT:\n    --\t\tprint_ref_status('!', \"[remote rejected]\", ref,\n    -+\t\tprint_ref_status('!', \"[remote rejected]\", NULL, ref,\n    - \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n    - \t\t\t\t ref->remote_status, porcelain, summary_width);\n    - \t\tbreak;\n    - \tcase REF_STATUS_EXPECTING_REPORT:\n    --\t\tprint_ref_status('!', \"[remote failure]\", ref,\n    -+\t\tprint_ref_status('!', \"[remote failure]\", NULL, ref,\n    - \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n    - \t\t\t\t \"remote failed to report status\",\n    - \t\t\t\t porcelain, summary_width);\n    - \t\tbreak;\n    - \tcase REF_STATUS_ATOMIC_PUSH_FAILED:\n    --\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n    -+\t\tprint_ref_status('!', \"[rejected]\", NULL, ref, ref->peer_ref,\n    - \t\t\t\t \"atomic push failed\", porcelain, summary_width);\n    - \t\tbreak;\n    - \tcase REF_STATUS_OK:\n\n6:  c5982067be < -:  ---------- t5412: test the proc-receive hook on HTTP protocol\n\n7:  823b7f2ea6 = 6:  b7d7175d89 doc: add documentation for the proc-receive hook\n\n---\n\nJiang Xin (6):\n  transport: not report a non-head push as a branch\n  receive-pack: add new proc-receive hook\n  refs.c: refactor to reuse ref_is_hidden()\n  receive-pack: new config receive.procReceiveRefs\n  receive-pack: refactor report for proc-receive\n  doc: add documentation for the proc-receive hook\n\n Documentation/config/receive.txt           |  14 +\n Documentation/githooks.txt                 |  70 ++\n Makefile                                   |   1 +\n builtin/receive-pack.c                     | 318 +++++++-\n refs.c                                     |  11 +-\n refs.h                                     |   1 +\n t/helper/test-proc-receive.c               | 175 +++++\n t/helper/test-tool.c                       |   1 +\n t/helper/test-tool.h                       |   1 +\n t/t5411-proc-receive-hook.sh               |  75 ++\n t/t5411/common-functions.sh                |  53 ++\n t/t5411/common-test-cases.sh               | 827 +++++++++++++++++++++\n t/t5412-proc-receive-hook-http-protocol.sh |  86 +++\n t/t5516-fetch-push.sh                      |   2 +-\n transport-helper.c                         |  64 +-\n transport.c                                |  34 +-\n 16 files changed, 1684 insertions(+), 49 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/common-test-cases.sh\n create mode 100755 t/t5412-proc-receive-hook-http-protocol.sh\n\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394913","messageId":"20200407120813.25025-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200404134345.10655-1-worldhello.net@gmail.com","subject":"[PATCH v9 1/6] transport: not report a non-head push as a branch","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-07T12:08:08Z","receivedAt":"2020-04-07T12:08:24Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing a new reference (not a head or tag), report it as a new\nreference instead of a new branch.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411-proc-receive-hook.sh               | 75 +++++++++++++++++++\n t/t5411/common-functions.sh                | 49 ++++++++++++\n t/t5411/common-test-cases.sh               | 43 +++++++++++\n t/t5412-proc-receive-hook-http-protocol.sh | 86 ++++++++++++++++++++++\n t/t5516-fetch-push.sh                      |  2 +-\n transport.c                                |  9 ++-\n 6 files changed, 260 insertions(+), 4 deletions(-)\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/common-test-cases.sh\n create mode 100755 t/t5412-proc-receive-hook-http-protocol.sh\n\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nnew file mode 100755\nindex 0000000000..09d516cf1b\n--- /dev/null\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -0,0 +1,75 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook'\n+\n+. ./test-lib.sh\n+\n+. \"$TEST_DIRECTORY\"/t5411/common-functions.sh\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  In addition to the common format method,\n+# we also replace URL of different protocol for the upstream repository to\n+# a fixed pattern.\n+make_user_friendly_and_stable_output () {\n+\tmake_user_friendly_and_stable_output_common | sed \\\n+\t\t-e \"s#To ../upstream.git#To <URL/of/upstream.git>#\"\n+}\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+test_expect_success \"setup\" '\n+\tgit init --bare upstream.git &&\n+\tgit init workbench &&\n+\tcreate_commits_in workbench A B &&\n+\t(\n+\t\tcd workbench &&\n+\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\tgit config core.abbrev 7 &&\n+\t\tgit remote add origin ../upstream.git &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit tag -m \"v123\" v123 $A &&\n+\t\tgit push origin \\\n+\t\t\t$B:refs/heads/master \\\n+\t\t\t$A:refs/heads/next\n+\t) &&\n+\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t# setup pre-receive hook\n+\tcat >upstream.git/hooks/pre-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# pre-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"pre-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\t# setup post-receive hook\n+\tcat >upstream.git/hooks/post-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# post-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"post-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\tchmod a+x \\\n+\t\tupstream.git/hooks/pre-receive \\\n+\t\tupstream.git/hooks/post-receive &&\n+\n+\tupstream=upstream.git\n+'\n+\n+# Include test cases for both file and HTTP protocol\n+. \"$TEST_DIRECTORY\"/t5411/common-test-cases.sh\n+\n+test_done\ndiff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\nnew file mode 100644\nindex 0000000000..e46884f74d\n--- /dev/null\n+++ b/t/t5411/common-functions.sh\n@@ -0,0 +1,49 @@\n+# Create commits in <repo> and assign each commit's oid to shell variables\n+# given in the arguments (A, B, and C). E.g.:\n+#\n+#     create_commits_in <repo> A B C\n+#\n+# NOTE: Never calling this function from a subshell since variable\n+# assignments will disappear when subshell exits.\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{} --)\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  We can easily prepare the expect text\n+# without having to worry about future changes of the commit ID and spaces\n+# of the output.  We also replce single quotes with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect txt.\n+make_user_friendly_and_stable_output_common () {\n+\tsed \\\n+\t\t-e \"s/  *\\$//\" \\\n+\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/'/\\\"/g\" \\\n+\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n+\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n+\t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n+\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n+\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\"\n+}\ndiff --git a/t/t5411/common-test-cases.sh b/t/t5411/common-test-cases.sh\nnew file mode 100644\nindex 0000000000..33b8541f28\n--- /dev/null\n+++ b/t/t5411/common-test-cases.sh\n@@ -0,0 +1,43 @@\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"normal git-push command\" '\n+\tgit -C workbench push -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tHEAD:refs/heads/master \\\n+\t\tHEAD:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo <URL/of/upstream.git>\n+\t + <OID>...<OID> HEAD -> master (forced update)\n+\t - [deleted] next\n+\t * [new tag] v123 -> v123\n+\t * [new reference] HEAD -> refs/review/master/topic\n+\t * [new branch] HEAD -> a/b/c\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5412-proc-receive-hook-http-protocol.sh b/t/t5412-proc-receive-hook-http-protocol.sh\nnew file mode 100755\nindex 0000000000..4883e69299\n--- /dev/null\n+++ b/t/t5412-proc-receive-hook-http-protocol.sh\n@@ -0,0 +1,86 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook for HTTP protocol'\n+\n+. ./test-lib.sh\n+\n+ROOT_PATH=\"$PWD\"\n+. \"$TEST_DIRECTORY\"/lib-gpg.sh\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+. \"$TEST_DIRECTORY\"/lib-terminal.sh\n+start_httpd\n+\n+. \"$TEST_DIRECTORY\"/t5411/common-functions.sh\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  In addition to the common format method,\n+# we also replace URL of different protocol for the upstream repository to\n+# a fixed pattern.\n+make_user_friendly_and_stable_output () {\n+\tmake_user_friendly_and_stable_output_common | sed \\\n+\t\t-e \"s#To http:.*/upstream.git#To <URL/of/upstream.git>#\"\n+}\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+test_expect_success \"setup\" '\n+\tgit init --bare upstream.git &&\n+\tgit -C upstream.git config http.receivepack true &&\n+\tgit init workbench &&\n+\tcreate_commits_in workbench A B &&\n+\t(\n+\t\tcd workbench &&\n+\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\tgit config core.abbrev 7 &&\n+\t\tgit remote add origin ../upstream.git &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit tag -m \"v123\" v123 $A &&\n+\t\tgit push origin \\\n+\t\t\t$B:refs/heads/master \\\n+\t\t\t$A:refs/heads/next\n+\t) &&\n+\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t# setup pre-receive hook\n+\tcat >upstream.git/hooks/pre-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# pre-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"pre-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\t# setup post-receive hook\n+\tcat >upstream.git/hooks/post-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# post-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"post-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\tchmod a+x \\\n+\t\tupstream.git/hooks/pre-receive \\\n+\t\tupstream.git/hooks/post-receive &&\n+\n+\tupstream=\"$HTTPD_DOCUMENT_ROOT_PATH/upstream.git\" &&\n+\tmv upstream.git \"$upstream\" &&\n+\tgit -C workbench remote set-url origin $HTTPD_URL/smart/upstream.git\n+'\n+\n+setup_askpass_helper\n+\n+# Include test cases for both file and HTTP protocol\n+. \"$TEST_DIRECTORY\"/t5411/common-test-cases.sh\n+\n+test_done\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 9ff041a093..9e4b9313b5 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1039,7 +1039,7 @@ test_force_fetch_tag \"annotated tag\" \"-f -a -m'tag message'\"\n test_expect_success 'push --porcelain' '\n \tmk_empty testrepo &&\n \techo >.git/foo  \"To testrepo\" &&\n-\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new branch]\"  &&\n+\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new reference]\"  &&\n \techo >>.git/foo \"Done\" &&\n \tgit push >.git/bar --porcelain  testrepo refs/heads/master:refs/remotes/origin/master &&\n \t(\ndiff --git a/transport.c b/transport.c\nindex 1fdc7dac1a..272c0f4046 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -500,9 +500,12 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\t\t\t porcelain, summary_width);\n \telse if (is_null_oid(&ref->old_oid))\n \t\tprint_ref_status('*',\n-\t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n-\t\t\t\"[new branch]\"),\n-\t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t  ? \"[new tag]\"\n+\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t     ? \"[new branch]\"\n+\t\t\t\t     : \"[new reference]\")),\n+\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394914","messageId":"20200407120813.25025-4-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200404134345.10655-1-worldhello.net@gmail.com","subject":"[PATCH v9 3/6] refs.c: refactor to reuse ref_is_hidden()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-07T12:08:10Z","receivedAt":"2020-04-07T12:08:24Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd new function `ref_is_matched()` to reuse `ref_is_hidden()`. Will use\nthis function for `receive-pack` to check commands with specific\nprefixes.\n\nTest case t5512 covered this change.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n refs.c | 11 ++++++++---\n refs.h |  1 +\n 2 files changed, 9 insertions(+), 3 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 1ab0bb54d3..229159ea1a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1389,13 +1389,18 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti\n }\n \n int ref_is_hidden(const char *refname, const char *refname_full)\n+{\n+\treturn ref_is_matched(hide_refs, refname, refname_full);\n+}\n+\n+int ref_is_matched(struct string_list *match_refs, const char *refname, const char *refname_full)\n {\n \tint i;\n \n-\tif (!hide_refs)\n+\tif (!match_refs)\n \t\treturn 0;\n-\tfor (i = hide_refs->nr - 1; i >= 0; i--) {\n-\t\tconst char *match = hide_refs->items[i].string;\n+\tfor (i = match_refs->nr - 1; i >= 0; i--) {\n+\t\tconst char *match = match_refs->items[i].string;\n \t\tconst char *subject;\n \t\tint neg = 0;\n \t\tconst char *p;\ndiff --git a/refs.h b/refs.h\nindex 545029c6d8..a2ea043f7f 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -739,6 +739,7 @@ int parse_hide_refs_config(const char *var, const char *value, const char *);\n  * parameter always points to the full ref name.\n  */\n int ref_is_hidden(const char *, const char *);\n+int ref_is_matched(struct string_list *, const char *, const char *);\n \n enum ref_type {\n \tREF_TYPE_PER_WORKTREE,\t  /* refs inside refs/ but not shared       */\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394915","messageId":"20200407120813.25025-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200404134345.10655-1-worldhello.net@gmail.com","subject":"[PATCH v9 2/6] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-07T12:08:09Z","receivedAt":"2020-04-07T12:08:26Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nGit calls an internal `execute_commands` function to handle commands\nsent from client to `git-receive-pack`.  Regardless of what references\nthe user pushes, git creates or updates the corresponding references if\nthe user has write-permission.  A contributor who has no\nwrite-permission, cannot push to the repository directly.  So, the\ncontributor has to write commits to an alternate location, and sends\npull request by emails or by other ways.  We call this workflow as a\ndistributed workflow.\n\nIt would be more convenient to work in a centralized workflow like what\nGerrit provided for some cases.  For example, a read-only user who\ncannot push to a branch directly can run the following `git push`\ncommand to push commits to a pseudo reference (has a prefix \"refs/for/\",\nnot \"refs/heads/\") to create a code review.\n\n    git push origin \\\n        HEAD:refs/for/<branch-name>/<session>\n\nThe `<branch-name>` in the above example can be as simple as \"master\",\nor a more complicated branch name like \"foo/bar\".  The `<session>` in\nthe above example command can be the local branch name of the client\nside, such as \"my/topic\".\n\nWe cannot implement a centralized workflow elegantly by using\n\"pre-receive\" + \"post-receive\", because Git will call the internal\nfunction \"execute_commands\" to create references (even the special\npseudo reference) between these two hooks.  Even though we can delete\nthe temporarily created pseudo reference via the \"post-receive\" hook,\nhaving a temporary reference is not safe for concurrent pushes.\n\nSo, add a filter and a new handler to support this kind of workflow.\nThe filter will check the prefix of the reference name, and if the\ncommand has a special reference name, the filter will turn a specific\nfield (`run_proc_receive`) on for the command.  Commands with this filed\nturned on will be executed by a new handler (an hook named\n\"proc-receive\") instead of the internal `execute_commands` function.\nWe can use this \"proc-receive\" command to create pull requests or send\nemails for code review.\n\nSuggested by Junio, this \"proc-receive\" hook reads the commands,\npush-options (optional), and send result using a protocol in pkt-line\nformat.  In the following example, The letter \"S\" stands for\n\"receive-pack\" and letter \"H\" stands for the hook.\n\n    # Version and capabilities negotiation.\n    S: PKT-LINE(version=1\\0push-options atomic...)\n    S: flush-pkt\n    H: PKT-LINE(version=1\\0push-options...)\n    H: flush-pkt\n\n    # Send commands from server to the hook.\n    S: PKT-LINE(old-oid new-oid ref)\n    S: ... ...\n    S: flush-pkt\n    # Only if push-options have been negotiated.\n    S: PKT-LINE(push-option)\n    S: ... ...\n    S: flush-pkt\n\n    # Receive result from the hook.\n    # OK, run this command successfully.\n    H: PKT-LINE(old-oid new-oid ref ok)\n    # NO, I reject it.\n    H: PKT-LINE(old-oid new-oid ref ng reason)\n    # OK, but use an alternate reference. (in latter commit)\n    H: PKT-LINE(old-oid new-oid ref ok ref:alt-ref)\n    # It will fallthrough to receive-pack to execute. (in latter commit)\n    H: PKT-LINE(old-oid new-oid ref ft)\n    H: ... ...\n    H: flush-pkt\n\nAfter receiving a command, the hook can create/update another alternate\nreference.  For example, a command for a reference \"refs/for/master\" may\ncreate a special reference, such as \"refs/pull/123/head\".  The alternate\nreference can be returned from the result in an extensible format like\n\"<old-oid> <new-oid> <reference> <status> [<message>]\".\n\nThe result will be stored in a command list, and \"receive-pack\" will use\nthe result to replace the commands that have specific `run_proc_receive`\nfield turned on.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Makefile                     |   1 +\n builtin/receive-pack.c       | 269 +++++++++++++++-\n t/helper/test-proc-receive.c | 175 +++++++++++\n t/helper/test-tool.c         |   1 +\n t/helper/test-tool.h         |   1 +\n t/t5411/common-functions.sh  |  10 +-\n t/t5411/common-test-cases.sh | 576 +++++++++++++++++++++++++++++++++++\n 7 files changed, 1027 insertions(+), 6 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n\ndiff --git a/Makefile b/Makefile\nindex ef1ff2228f..1c52c280ce 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -725,6 +725,7 @@ TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\n+TEST_BUILTINS_OBJS += test-proc-receive.o\n TEST_BUILTINS_OBJS += test-progress.o\n TEST_BUILTINS_OBJS += test-reach.o\n TEST_BUILTINS_OBJS += test-read-cache.o\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 2cc18bbffd..f3a2c2612d 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -308,11 +308,14 @@ static void write_head_info(void)\n \tpacket_flush(1);\n }\n \n+#define RUN_PROC_RECEIVE_SCHEDULED\t1\n+#define RUN_PROC_RECEIVE_RETURNED\t2\n struct command {\n \tstruct command *next;\n \tconst char *error_string;\n \tunsigned int skip_update:1,\n-\t\t     did_not_exist:1;\n+\t\t     did_not_exist:1,\n+\t\t     run_proc_receive:2;\n \tint index;\n \tstruct object_id old_oid;\n \tstruct object_id new_oid;\n@@ -817,6 +820,239 @@ static int run_update_hook(struct command *cmd)\n \treturn finish_command(&proc);\n }\n \n+static struct command *find_command_by_refname(const struct command *list,\n+\t\t\t\t\t       const char *refname)\n+{\n+\tfor ( ; list; list = list->next)\n+\t\tif (!strcmp(list->ref_name, refname))\n+\t\t\treturn (struct command *)list;\n+\treturn NULL;\n+}\n+\n+static int read_proc_receive_result(struct packet_reader *reader,\n+\t\t\t\t    struct command *commands,\n+\t\t\t\t    struct strbuf *errmsg)\n+{\n+\tstruct command *hint;\n+\tstruct command *cmd;\n+\tint code = 0;\n+\n+\thint = NULL;\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\t\tchar *status;\n+\t\tchar *msg = NULL;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ') {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\\n\",\n+\t\t\t\t    reader->line);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\trefname = p;\n+\t\tstatus = strchr(p, ' ');\n+\t\tif (!status) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\\n\",\n+\t\t\t\t    reader->line);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\t*status++ = '\\0';\n+\t\tif (strlen(status) > 2 && *(status + 2) == ' ') {\n+\t\t\tmsg = status + 2;\n+\t\t\t*msg++ = '\\0';\n+\t\t}\n+\t\tif (strlen(status) != 2) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n+\t\t\t\t    status, reader->line);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/* first try searching at our hint, falling back to all refs */\n+\t\tif (hint)\n+\t\t\thint = find_command_by_refname(hint, refname);\n+\t\tif (!hint)\n+\t\t\thint = find_command_by_refname(commands, refname);\n+\t\tif (!hint) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unknown ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!hint->run_proc_receive) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on ref of builtin command: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(status, \"ng\")) {\n+\t\t\tif (msg)\n+\t\t\t\thint->error_string = xstrdup(msg);\n+\t\t\telse\n+\t\t\t\thint->error_string = \"failed\";\n+\t\t\tcode = -1;\n+\t\t} else if (strcmp(\"ok\", status)) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n+\t\t\t\t    status, reader->line);\n+\t\t\tcode = -1;\n+\t\t\t/* Skip marking it as RUN_PROC_RECEIVE_RETURNED */\n+\t\t\tcontinue;\n+\t\t}\n+\t\toidcpy(&hint->old_oid, &old_oid);\n+\t\toidcpy(&hint->new_oid, &new_oid);\n+\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n+\t}\n+\n+\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\tif (cmd->run_proc_receive &&\n+\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {\n+\t\t    cmd->error_string = \"no report from proc-receive\";\n+\t\t    code = -1;\n+\t\t}\n+\n+\treturn code;\n+}\n+\n+static int run_proc_receive_hook(struct command *commands,\n+\t\t\t\t const struct string_list *push_options)\n+{\n+\tstruct child_process proc = CHILD_PROCESS_INIT;\n+\tstruct async muxer;\n+\tstruct command *cmd;\n+\tconst char *argv[2];\n+\tstruct packet_reader reader;\n+\tstruct strbuf cap = STRBUF_INIT;\n+\tstruct strbuf errmsg = STRBUF_INIT;\n+\tint pr_use_push_options = 0;\n+\tint version = 0;\n+\tint code;\n+\n+\targv[0] = find_hook(\"proc-receive\");\n+\tif (!argv[0]) {\n+\t\trp_error(\"cannot find hook 'proc-receive'\");\n+\t\treturn -1;\n+\t}\n+\targv[1] = NULL;\n+\n+\tproc.argv = argv;\n+\tproc.in = -1;\n+\tproc.out = -1;\n+\tproc.trace2_hook_name = \"proc-receive\";\n+\n+\tif (use_sideband) {\n+\t\tmemset(&muxer, 0, sizeof(muxer));\n+\t\tmuxer.proc = copy_to_sideband;\n+\t\tmuxer.in = -1;\n+\t\tcode = start_async(&muxer);\n+\t\tif (code)\n+\t\t\treturn code;\n+\t\tproc.err = muxer.in;\n+\t} else {\n+\t\tproc.err = 0;\n+\t}\n+\n+\tcode = start_command(&proc);\n+\tif (code) {\n+\t\tif (use_sideband)\n+\t\t\tfinish_async(&muxer);\n+\t\treturn code;\n+\t}\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\n+\t/* Version negotiaton */\n+\tpacket_reader_init(&reader, proc.out, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\tif (use_atomic)\n+\t\tstrbuf_addstr(&cap, \" atomic\");\n+\tif (use_push_options)\n+\t\tstrbuf_addstr(&cap, \" push-options\");\n+\tif (cap.len) {\n+\t\tpacket_write_fmt(proc.in, \"version=1%c%s\\n\", '\\0', cap.buf + 1);\n+\t\tstrbuf_release(&cap);\n+\t} else {\n+\t\tpacket_write_fmt(proc.in, \"version=1\\n\");\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader.pktlen > 8 && starts_with(reader.line, \"version=\")) {\n+\t\t\tversion = atoi(reader.line + 8);\n+\t\t\tlinelen = strlen(reader.line);\n+\t\t\tif (linelen < reader.pktlen) {\n+\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tpr_use_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (version != 1) {\n+\t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n+\t\t\t    version);\n+\t\tcode = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\t/* Send commands */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tchar *old_hex, *new_hex;\n+\n+\t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)\n+\t\t\tcontinue;\n+\t\told_hex = oid_to_hex(&cmd->old_oid);\n+\t\tnew_hex = oid_to_hex(&cmd->new_oid);\n+\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n+\t\t\t\t old_hex, new_hex, cmd->ref_name);\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\t/* Send push options */\n+\tif (pr_use_push_options) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tfor_each_string_list_item(item, push_options)\n+\t\t\tpacket_write_fmt(proc.in, \"%s\", item->string);\n+\t\tpacket_flush(proc.in);\n+\t}\n+\n+\t/* Read result from proc-receive */\n+\tcode = read_proc_receive_result(&reader, commands, &errmsg);\n+\n+cleanup:\n+\tclose(proc.in);\n+\tclose(proc.out);\n+\tif (use_sideband)\n+\t\tfinish_async(&muxer);\n+\tif (finish_command(&proc))\n+\t\tcode = -1;\n+\tif (errmsg.len >0) {\n+\t\tchar *p = errmsg.buf;\n+\n+\t\tp += errmsg.len - 1;\n+\t\tif (*p == '\\n')\n+\t\t\t*p = '\\0';\n+\t\trp_error(\"%s\", errmsg.buf);\n+\t\tstrbuf_release(&errmsg);\n+\t}\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn code;\n+}\n+\n static char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n@@ -1392,7 +1628,7 @@ static void execute_commands_non_atomic(struct command *commands,\n \tstruct strbuf err = STRBUF_INIT;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\ttransaction = ref_transaction_begin(&err);\n@@ -1432,7 +1668,7 @@ static void execute_commands_atomic(struct command *commands,\n \t}\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\tcmd->error_string = update(cmd, si);\n@@ -1468,6 +1704,7 @@ static void execute_commands(struct command *commands,\n \tstruct iterate_data data;\n \tstruct async muxer;\n \tint err_fd = 0;\n+\tint run_proc_receive = 0;\n \n \tif (unpacker_error) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n@@ -1497,6 +1734,20 @@ static void execute_commands(struct command *commands,\n \n \treject_updates_to_hidden(commands);\n \n+\t/* Try to find commands that have special prefix in their reference names,\n+\t * and mark them to run an external \"proc-receive\" hook later.\n+\t */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!should_process_cmd(cmd))\n+\t\t\tcontinue;\n+\n+\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n+\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n+\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\trun_proc_receive = 1;\n+\t\t}\n+\t}\n+\n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n \t\t\tif (!cmd->error_string)\n@@ -1523,6 +1774,18 @@ static void execute_commands(struct command *commands,\n \tfree(head_name_to_free);\n \thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, NULL, NULL);\n \n+\tif (run_proc_receive) {\n+\t\tint code;\n+\n+\t\tcode = run_proc_receive_hook(commands, push_options);\n+\t\tif (code) {\n+\t\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\t\tif (!cmd->error_string  && (cmd->run_proc_receive || use_atomic))\n+\t\t\t\t\tcmd->error_string = \"fail to run proc-receive hook\";\n+\t\t\t}\n+\t\t}\n+\t}\n+\n \tif (use_atomic)\n \t\texecute_commands_atomic(commands, si);\n \telse\ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nnew file mode 100644\nindex 0000000000..ca44b05bd0\n--- /dev/null\n+++ b/t/helper/test-proc-receive.c\n@@ -0,0 +1,175 @@\n+#include \"cache.h\"\n+#include \"connect.h\"\n+#include \"parse-options.h\"\n+#include \"pkt-line.h\"\n+#include \"sigchain.h\"\n+#include \"string-list.h\"\n+#include \"test-tool.h\"\n+\n+static const char *proc_receive_usage[] = {\n+\t\"test-tool proc-receive [<options>...]\",\n+\tNULL\n+};\n+\n+static int version = 1;\n+static int verbose = 0;\n+static int no_push_options = 0;\n+static int use_atomic = 0;\n+static int use_push_options = 0;\n+static struct string_list returns = STRING_LIST_INIT_NODUP;\n+\n+struct command {\n+\tstruct command *next;\n+\tconst char *error_string;\n+\tunsigned int skip_update:1,\n+\t\t     did_not_exist:1;\n+\tint index;\n+\tstruct object_id old_oid;\n+\tstruct object_id new_oid;\n+\tchar ref_name[FLEX_ARRAY]; /* more */\n+};\n+\n+static void proc_receive_verison(struct packet_reader *reader) {\n+\tint server_version = 0;\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader->pktlen > 8 && starts_with(reader->line, \"version=\")) {\n+\t\t\tserver_version = atoi(reader->line+8);\n+\t\t\tlinelen = strlen(reader->line);\n+\t\t\tif (linelen < reader->pktlen) {\n+\t\t\t\tconst char *feature_list = reader->line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"atomic\"))\n+\t\t\t\t\tuse_atomic= 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tuse_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (server_version != 1)\n+\t\tdie(\"bad protocol version: %d\", server_version);\n+\n+\tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n+\t\t\t version, '\\0',\n+\t\t\t use_push_options && !no_push_options ? \"push-options\": \"\");\n+\tpacket_flush(1);\n+}\n+\n+static void proc_receive_read_commands(struct packet_reader *reader,\n+\t\t\t\t       struct command **commands)\n+{\n+\tstruct command **tail = commands;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tstruct command *cmd;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ')\n+\t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n+\t\t\t    reader->line);\n+\t\trefname = p;\n+\t\tFLEX_ALLOC_STR(cmd, ref_name, refname);\n+\t\toidcpy(&cmd->old_oid, &old_oid);\n+\t\toidcpy(&cmd->new_oid, &new_oid);\n+\n+\t\t*tail = cmd;\n+\t\ttail = &cmd->next;\n+\t}\n+}\n+\n+static void proc_receive_read_push_options(struct packet_reader *reader,\n+\t\t\t\t\t   struct string_list *options)\n+{\n+\n+\tif (no_push_options || !use_push_options)\n+\t       return;\n+\n+\twhile (1) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tstring_list_append(options, reader->line);\n+\t}\n+}\n+\n+int cmd__proc_receive(int argc, const char **argv)\n+{\n+\tstruct packet_reader reader;\n+\tstruct command *commands;\n+\tstruct string_list push_options = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"no-push-options\", &no_push_options,\n+\t\t\t \"disable push options\"),\n+\t\tOPT_STRING_LIST('r', \"return\", &returns, \"old/new/ref/status/msg\",\n+\t\t\t\t\"return of results\"),\n+\t\tOPT__VERBOSE(&verbose, \"be verbose\"),\n+\t\tOPT_INTEGER('V', \"version\", &version,\n+\t\t\t    \"use this protocol version number\"),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, \"test-tools\", options, proc_receive_usage, 0);\n+\tif (argc > 0)\n+\t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n+\n+\tpacket_reader_init(&reader, 0, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\tproc_receive_verison(&reader);\n+\tproc_receive_read_commands(&reader, &commands);\n+\tproc_receive_read_push_options(&reader, &push_options);\n+\n+\tif (verbose) {\n+\t\tstruct command *cmd;\n+\n+\t\tif (use_push_options || use_atomic)\n+\t\t\tfprintf(stderr, \"proc-receive:%s%s\\n\",\n+\t\t\t\tuse_atomic? \" atomic\": \"\",\n+\t\t\t\tuse_push_options ? \" push_options\": \"\");\n+\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tchar *old_hex, *new_hex;\n+\n+\t\t\told_hex = oid_to_hex(&cmd->old_oid);\n+\t\t\tnew_hex = oid_to_hex(&cmd->new_oid);\n+\t\t\tfprintf(stderr, \"proc-receive< %s %s %s\\n\",\n+\t\t\t\told_hex, new_hex, cmd->ref_name);\n+\t\t}\n+\n+\t\tif (push_options.nr > 0) {\n+\t\t\tfor_each_string_list_item(item, &push_options)\n+\t\t\t\tfprintf(stderr, \"proc-receive< %s\\n\", item->string);\n+\t\t}\n+\n+\t\tif (returns.nr) {\n+\t\t\tfor_each_string_list_item(item, &returns)\n+\t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n+\t\t}\n+\t}\n+\n+\tif (returns.nr) {\n+\t\tfor_each_string_list_item(item, &returns)\n+\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n+\t}\n+\tpacket_flush(1);\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 31eedcd241..f865433a61 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -44,6 +44,7 @@ static struct test_cmd cmds[] = {\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\n+\t{ \"proc-receive\", cmd__proc_receive},\n \t{ \"progress\", cmd__progress },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 4eb5e6609e..059008cf22 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -34,6 +34,7 @@ int cmd__parse_pathspec_file(int argc, const char** argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\n+int cmd__proc_receive(int argc, const char **argv);\n int cmd__progress(int argc, const char **argv);\n int cmd__reach(int argc, const char **argv);\n int cmd__read_cache(int argc, const char **argv);\ndiff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\nindex e46884f74d..6ccab42c6c 100644\n--- a/t/t5411/common-functions.sh\n+++ b/t/t5411/common-functions.sh\n@@ -34,8 +34,11 @@ create_commits_in () {\n # Format the output of git-push, git-show-ref and other commands to make a\n # user-friendly and stable text.  We can easily prepare the expect text\n # without having to worry about future changes of the commit ID and spaces\n-# of the output.  We also replce single quotes with double quotes, because\n-# it is boring to prepare unquoted single quotes in expect txt.\n+# of the output.  Single quotes are replaced with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect txt.  We also\n+# remove some locale error messages, which break test if we turn on\n+# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n+# on plumbing commands.\n make_user_friendly_and_stable_output_common () {\n \tsed \\\n \t\t-e \"s/  *\\$//\" \\\n@@ -45,5 +48,6 @@ make_user_friendly_and_stable_output_common () {\n \t\t-e \"s/$B/<COMMIT-B>/g\" \\\n \t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n \t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n-\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\"\n+\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\" \\\n+\t\t-e \"/^error: / d\"\n }\ndiff --git a/t/t5411/common-test-cases.sh b/t/t5411/common-test-cases.sh\nindex 33b8541f28..08c7abf11a 100644\n--- a/t/t5411/common-test-cases.sh\n+++ b/t/t5411/common-test-cases.sh\n@@ -41,3 +41,579 @@ test_expect_success \"normal git-push command\" '\n \tEOF\n \ttest_cmp expect actual\n '\n+\n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"no proc-receive hook, fail to push special ref\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n+test_expect_success \"no proc-receive hook, fail all for atomic push\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\t$B:master \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] <COMMIT-B> -> master (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad version)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive bad protocol: unknown version\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\t# Check status report for git-push\n+\tsed -n \\\n+\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n+\t\t-e \"/^ / { s/   */ /g; p; }\" \\\n+\t\t<actual >actual-report &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual-report &&\n+\n+\t# Check error message from \"receive-pack\", but ignore unstable fatal error\n+\t# message (\"remote: fatal: the remote end hung up unexpectedly\") which\n+\t# is different from the remote HTTP server with different locale settings.\n+\tsed -n -e \"/^remote: error:/ { s/   */ /g; p; }\" \\\n+\t\t<actual >actual-error &&\n+\tcat >expect <<-EOF &&\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tEOF\n+\ttest_cmp expect actual-error &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive bad protocol: no report\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad oid)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"bad-id new-id ref ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: bad oid\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> bad-id new-id ref ok\n+\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"bad-id new-id ref ok\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: no status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic xx msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: unknown status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic xx msg\n+\tremote: error: proc-receive has bad status \"xx\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic bad status\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: bad status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic bad status\n+\tremote: error: proc-receive has bad status \"bad status\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (no message)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (has message)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng error msg\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (report status on builtin command)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/heads/master ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: warning on report for builtin command\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/heads/master ok\n+\tremote: error: proc-receive reported status on ref of builtin command: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t <OID>..<OID> <COMMIT-B> -> master\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\n+\n+test_expect_success \"setup proc-receive hook (ok)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: no report from proc-receive\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"not support push options\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n+\t\tactual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"push with options\" '\n+\tgit -C workbench push \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394917","messageId":"20200407120813.25025-5-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200404134345.10655-1-worldhello.net@gmail.com","subject":"[PATCH v9 4/6] receive-pack: new config receive.procReceiveRefs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-07T12:08:11Z","receivedAt":"2020-04-07T12:08:29Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd a new multi-valued config variable \"receive.procReceiveRefs\"\nfor `receive-pack` command, like the follows:\n\n    git config --system --add receive.procReceiveRefs refs/for/\n    git config --system --add receive.procReceiveRefs refs/drafts/\n\nIf the specific prefix strings match the reference names of the commands\nwhich are sent by git client to `receive-pack`, these commands will be\nexecuted by an external hook (named \"proc-receive\"), instead of the\ninternal `execute_commands` function.\n\nFor example, if it is set to \"refs/for/\", pushing to a reference such as\n\"refs/for/master\" will not create or update reference \"refs/for/master\",\nbut may create or update a pull request directly by running the external\nhook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/config/receive.txt |  14 ++++\n builtin/receive-pack.c           |  43 ++++++++--\n t/t5411/common-test-cases.sh     | 130 +++++++++++++++++++++++++++++++\n 3 files changed, 180 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/receive.txt b/Documentation/config/receive.txt\nindex 65f78aac37..4f993ee376 100644\n--- a/Documentation/config/receive.txt\n+++ b/Documentation/config/receive.txt\n@@ -114,6 +114,20 @@ receive.hideRefs::\n \tAn attempt to update or delete a hidden ref by `git push` is\n \trejected.\n \n+receive.procReceiveRefs::\n+\tThis is a multi-valued variable that defines reference prefixes\n+\tto match the commands in `receive-pack`.  Commands matching the\n+\tprefixes will be executed by an external hooks \"proc-receive\",\n+\tinstead of the internal `execute_commands` function.  If this\n+\tvariable is not defined, the \"proc-receive\" hook will never be\n+\tused, and all commands will be executed by the internal\n+\t`execute_commands` function.\n+\n+\tFor example, if this variable is set to \"refs/for\", pushing to\n+\treference such as \"refs/for/master\" will not create or update a\n+\treference named \"refs/for/master\", but may create or update a\n+\tpull request directly by running an external hook.\n+\n receive.updateServerInfo::\n \tIf set to true, git-receive-pack will run git-update-server-info\n \tafter receiving data from git-push and updating refs.\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex f3a2c2612d..a78daa6733 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -76,6 +76,7 @@ static struct object_id push_cert_oid;\n static struct signature_check sigcheck;\n static const char *push_cert_nonce;\n static const char *cert_nonce_seed;\n+static struct string_list proc_receive_refs;\n \n static const char *NONCE_UNSOLICITED = \"UNSOLICITED\";\n static const char *NONCE_BAD = \"BAD\";\n@@ -228,6 +229,20 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (strcmp(var, \"receive.procreceiverefs\") == 0) {\n+\t\tchar *prefix;\n+\t\tint len;\n+\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tprefix = xstrdup(value);\n+\t\tlen = strlen(prefix);\n+\t\twhile (len && prefix[len - 1] == '/')\n+\t\t\tprefix[--len] = '\\0';\n+\t\tstring_list_insert(&proc_receive_refs, prefix);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -1737,15 +1752,26 @@ static void execute_commands(struct command *commands,\n \t/* Try to find commands that have special prefix in their reference names,\n \t * and mark them to run an external \"proc-receive\" hook later.\n \t */\n-\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n-\t\t\tcontinue;\n+\tif (proc_receive_refs.nr > 0) {\n+\t\tstruct strbuf refname_full = STRBUF_INIT;\n+\t\tsize_t prefix_len;\n+\n+\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n+\t\tprefix_len = refname_full.len;\n \n-\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n-\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n-\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n-\t\t\trun_proc_receive = 1;\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tif (!should_process_cmd(cmd))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_setlen(&refname_full, prefix_len);\n+\t\t\tstrbuf_addstr(&refname_full, cmd->ref_name);\n+\t\t\tif (ref_is_matched(&proc_receive_refs, cmd->ref_name, refname_full.buf)) {\n+\t\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\t\trun_proc_receive = 1;\n+\t\t\t}\n \t\t}\n+\n+\t\tstrbuf_release(&refname_full);\n \t}\n \n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n@@ -2205,6 +2231,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n+\tstring_list_init(&proc_receive_refs, 0);\n+\n \tpacket_trace_identity(\"receive-pack\");\n \n \targc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);\n@@ -2320,5 +2348,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \toid_array_clear(&shallow);\n \toid_array_clear(&ref);\n \tfree((void *)push_cert_nonce);\n+\tstring_list_clear(&proc_receive_refs, 0);\n \treturn 0;\n }\ndiff --git a/t/t5411/common-test-cases.sh b/t/t5411/common-test-cases.sh\nindex 08c7abf11a..a9e80c374b 100644\n--- a/t/t5411/common-test-cases.sh\n+++ b/t/t5411/common-test-cases.sh\n@@ -53,6 +53,14 @@ test_expect_success \"cleanup\" '\n \t)\n '\n \n+test_expect_success \"add two receive.procReceiveRefs settings\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit config --add receive.procReceiveRefs refs/for &&\n+\t\tgit config --add receive.procReceiveRefs refs/review/\n+\t)\n+'\n+\n # Refs of upstream : master(A)\n # Refs of workbench: master(A)  tags/v123\n # git push         :                       next(A)  refs/for/master/topic(A)\n@@ -617,3 +625,125 @@ test_expect_success \"push with options\" '\n \tEOF\n \ttest_cmp expect actual\n '\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/review/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"report update of all special refs\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/review/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/review/a/b/c/topic\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"report mixed refs update\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> bar\n+\t * [new branch] HEAD -> baz\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new branch] HEAD -> foo\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394916","messageId":"20200407120813.25025-7-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200404134345.10655-1-worldhello.net@gmail.com","subject":"[PATCH v9 6/6] doc: add documentation for the proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-07T12:08:13Z","receivedAt":"2020-04-07T12:08:30Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd documentation for the new \"proc-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/githooks.txt | 70 ++++++++++++++++++++++++++++++++++++++\n 1 file changed, 70 insertions(+)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 3dccab5375..10ea5c1f18 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -333,6 +333,76 @@ The default 'update' hook, when enabled--and with\n `hooks.allowunannotated` config option unset or set to false--prevents\n unannotated tags to be pushed.\n \n+[[proc-receive]]\n+proc-receive\n+~~~~~~~~~~~~\n+This hook is invoked by linkgit:git-receive-pack[1] when it reacts to\n+special `git push` command.  According to refnames of the commands which\n+`git push` sends to 'git-receive-pack', the commands will be devided\n+into two groups by matching what the `receive.procReceiveRefs`\n+configuration variable defines.  One group of the commands will execute\n+the internal `execute_commands` function to update the corresponding\n+refnames, and the other group of commands which have matching refnames\n+will execute this 'proc-receive' hook to create pull requests, etc.\n+If there is no `receive.procReceiveRefs` settings, this hook won't\n+execute at all, and all commands are sent to the internal\n+`execute_commands` function.\n+\n+Its exit status only determines the success or failure of the group of\n+commands with special refnames, unless atomic push is in use.\n+\n+This hook executes once for the receive operation.  It takes no\n+arguments, but will talk a protocol in pkt-line format with the\n+'receive-pack' for reading commands, push-options (optional), and\n+sending result.  In the following example, The letter \"S\" stands for\n+\"receive-pack\" and letter \"H\" stands for the hook.\n+\n+    # Version and capabilities negotiation.\n+    S: PKT-LINE(version=1\\0push-options atomic...)\n+    S: flush-pkt\n+    H: PKT-LINE(version=1\\0push-options...)\n+    H: flush-pkt\n+\n+    # Send commands from server to the hook.\n+    S: PKT-LINE(old-oid new-oid ref)\n+    S: ... ...\n+    S: flush-pkt\n+    # Only if push-options have been negotiated.\n+    S: PKT-LINE(push-option)\n+    S: ... ...\n+    S: flush-pkt\n+\n+    # Receive result from the hook.\n+    # OK, run this command successfully.\n+    H: PKT-LINE(old-oid new-oid ref ok)\n+    # NO, I reject it.\n+    H: PKT-LINE(old-oid new-oid ref ng reason)\n+    # OK, but use an alternate reference.\n+    H: PKT-LINE(old-oid new-oid ref ok ref:alt-ref)\n+    # It will fallthrough to receive-pack to execute.\n+    H: PKT-LINE(old-oid new-oid ref ft)\n+    H: ... ...\n+    H: flush-pkt\n+\n+The \"proc-receive\" hook may update one or more references, and will send\n+its result one by one in pkt-line format.  Each line of the result has\n+four fields and one optional message field, like \"<old-oid> <new-oid>\n+<ref> <status> [<message>]\".\n+\n+The first three fields are the same as those of the commands for\n+\"receive-pack\".\n+\n+The forth field has a two-letter status code.  Available status codes:\n+\n+* ok: The command runs successfully.  If the optional message has a\n+  prefix \"ref:\", the hook has created/updated an alternate reference\n+  instead.\n+\n+* ng: Fail to run the command.  Error message is given in the optional\n+  message field.\n+\n+* ft: Will fallthrough to receive-pack to execute.\n+\n [[post-receive]]\n post-receive\n ~~~~~~~~~~~~\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"394918","messageId":"20200407120813.25025-6-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200404134345.10655-1-worldhello.net@gmail.com","subject":"[PATCH v9 5/6] receive-pack: refactor report for proc-receive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-07T12:08:12Z","receivedAt":"2020-04-07T12:08:31Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThe \"proc-receive\" may update one or more references, and will send its\nresult one by one in pkt-line format.  Each line of the result has four\nfields and one optional message field, as \"<old-oid> <new-oid> <ref>\n<status> [<message>]\".  See the following example:\n\n    # OK, run this command successfully.\n    PKT-LINE(old-oid new-oid ref ok)\n\n    # NO, I reject it.\n    PKT-LINE(old-oid new-oid ref ng reason)\n\n    # OK, but use an alternate reference.\n    PKT-LINE(old-oid new-oid ref ok ref:alt-ref)\n\n    # It will fallthrough to receive-pack to execute.\n    PKT-LINE(old-oid new-oid ref ft)\n\nThe first three fields have the same foramt as a command.\n\nThe forth field has a two-letter status code.  Available status code:\n\n* ok: The command runs successfully.  If the optional message has a\n  prefix \"ref:\", the hook has created/updated an alternate reference\n  instead.\n\n* ng: Fail to run the command. Error message is in the optional message\n  field.\n\n* ft: Will fallthrough to receive-pack to execute.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c       | 24 +++++++---\n t/t5411/common-test-cases.sh | 90 +++++++++++++++++++++++++++++++++---\n transport-helper.c           | 64 ++++++++++++-------------\n transport.c                  | 25 ++++++++--\n 4 files changed, 156 insertions(+), 47 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex a78daa6733..73acb1b5c8 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -328,6 +328,7 @@ static void write_head_info(void)\n struct command {\n \tstruct command *next;\n \tconst char *error_string;\n+\tconst char *extra_string;\n \tunsigned int skip_update:1,\n \t\t     did_not_exist:1,\n \t\t     run_proc_receive:2;\n@@ -913,7 +914,12 @@ static int read_proc_receive_result(struct packet_reader *reader,\n \t\t\telse\n \t\t\t\thint->error_string = \"failed\";\n \t\t\tcode = -1;\n-\t\t} else if (strcmp(\"ok\", status)) {\n+\t\t} else if (!strcmp(\"ok\", status)) {\n+\t\t\thint->extra_string = xstrdup_or_null(msg);\n+\t\t} else if (!strcmp(\"ft\", status)) {\n+\t\t\t/* Reset \"run_proc_receive\" field, and continue to run in \"receive-pack\" */\n+\t\t\thint->run_proc_receive = 0;\n+\t\t} else {\n \t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n \t\t\t\t    status, reader->line);\n \t\t\tcode = -1;\n@@ -922,7 +928,8 @@ static int read_proc_receive_result(struct packet_reader *reader,\n \t\t}\n \t\toidcpy(&hint->old_oid, &old_oid);\n \t\toidcpy(&hint->new_oid, &new_oid);\n-\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n+\t\tif (hint->run_proc_receive)\n+\t\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n \t}\n \n \tfor (cmd = commands; cmd; cmd = cmd->next)\n@@ -2188,12 +2195,17 @@ static void report(struct command *commands, const char *unpack_status)\n \tpacket_buf_write(&buf, \"unpack %s\\n\",\n \t\t\t unpack_status ? unpack_status : \"ok\");\n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!cmd->error_string)\n-\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n-\t\t\t\t\t cmd->ref_name);\n-\t\telse\n+\t\tif (!cmd->error_string) {\n+\t\t\tif (!cmd->extra_string)\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n+\t\t\t\t\t\t cmd->ref_name);\n+\t\t\telse\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s%c%s\\n\",\n+\t\t\t\t\t\t cmd->ref_name, ' ', cmd->extra_string);\n+\t\t} else {\n \t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n \t\t\t\t\t cmd->ref_name, cmd->error_string);\n+\t\t}\n \t}\n \tpacket_buf_flush(&buf);\n \ndiff --git a/t/t5411/common-test-cases.sh b/t/t5411/common-test-cases.sh\nindex a9e80c374b..a2a16c0ed4 100644\n--- a/t/t5411/common-test-cases.sh\n+++ b/t/t5411/common-test-cases.sh\n@@ -640,8 +640,8 @@ test_expect_success \"setup proc-receive hook\" '\n \n \ttest-tool proc-receive -v \\\n \t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n-\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n-\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok ref:refs/pull/123/head\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok ref:refs/pull/124/head\"\n \tEOF\n \tchmod a+x \"$upstream/hooks/proc-receive\"\n '\n@@ -666,16 +666,16 @@ test_expect_success \"report update of all special refs\" '\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n-\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n-\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok ref:refs/pull/123/head\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok ref:refs/pull/124/head\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \t * [new reference] HEAD -> refs/review/a/b/c/topic\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/pull/124/head\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -704,6 +704,7 @@ test_expect_success \"setup proc-receive hook\" '\n # git push         :                       bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n test_expect_success \"report mixed refs update\" '\n \tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n \t\tHEAD:refs/heads/bar \\\n \t\tHEAD:refs/heads/baz \\\n \t\tHEAD:refs/for/next/topic \\\n@@ -713,6 +714,7 @@ test_expect_success \"report mixed refs update\" '\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n@@ -724,12 +726,14 @@ test_expect_success \"report mixed refs update\" '\n \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n \tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n+\t <OID>..<OID> <COMMIT-B> -> master\n \t * [new branch] HEAD -> bar\n \t * [new branch] HEAD -> baz\n \t * [new reference] HEAD -> refs/for/next/topic\n@@ -743,6 +747,80 @@ test_expect_success \"report mixed refs update\" '\n \t<COMMIT-A> refs/heads/bar\n \t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"config receive.procReceiveRefs for all ref/\" '\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs/\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$B $A refs/heads/master ft\" \\\n+\t\t-r \"$A $ZERO_OID refs/heads/foo ft\" \\\n+\t\t-r \"$A $B refs/heads/bar ft\" \\\n+\t\t-r \"$A $B refs/for/master/topic ok ref:refs/pull/123/head\" \\\n+\t\t-r \"$B $A refs/for/next/topic ok ref:refs/pull/124/head\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      :                       (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"report test: fallthrough\" '\n+\tgit -C workbench push -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/heads/master ft\n+\tremote: proc-receive> <COMMIT-A> <ZERO-OID> refs/heads/foo ft\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/heads/bar ft\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/for/master/topic ok ref:refs/pull/123/head\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/next/topic ok ref:refs/pull/124/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/next/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID>..<OID> <COMMIT-B> -> bar\n+\t - [deleted] foo\n+\t + <OID>...<OID> HEAD -> master (forced update)\n+\t * [new reference] HEAD -> refs/pull/123/head\n+\t * [new reference] HEAD -> refs/pull/124/head\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/master\n \tEOF\n \ttest_cmp expect actual\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 20a7185ec4..cec3495d59 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -747,37 +747,39 @@ static int push_update_ref_status(struct strbuf *buf,\n \t\t\tmsg = xstrdup(msg);\n \t\tstrbuf_release(&msg_buf);\n \n-\t\tif (!strcmp(msg, \"no match\")) {\n-\t\t\tstatus = REF_STATUS_NONE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"up to date\")) {\n-\t\t\tstatus = REF_STATUS_UPTODATE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"non-fast forward\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_NONFASTFORWARD;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"already exists\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_ALREADY_EXISTS;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"fetch first\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_FETCH_FIRST;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"needs force\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_NEEDS_FORCE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"stale info\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_STALE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"forced update\")) {\n-\t\t\tforced = 1;\n-\t\t\tFREE_AND_NULL(msg);\n+\t\tif (status != REF_STATUS_OK) {\n+\t\t\tif (!strcmp(msg, \"no match\")) {\n+\t\t\t\tstatus = REF_STATUS_NONE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"up to date\")) {\n+\t\t\t\tstatus = REF_STATUS_UPTODATE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"non-fast forward\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_NONFASTFORWARD;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"already exists\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_ALREADY_EXISTS;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"fetch first\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_FETCH_FIRST;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"needs force\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_NEEDS_FORCE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"stale info\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_STALE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"forced update\")) {\n+\t\t\t\tforced = 1;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n \t\t}\n \t}\n \ndiff --git a/transport.c b/transport.c\nindex 272c0f4046..97b7c6a442 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -463,11 +463,28 @@ static void print_ref_status(char flag, const char *summary,\n \t\t\t     struct ref *to, struct ref *from, const char *msg,\n \t\t\t     int porcelain, int summary_width)\n {\n+\tchar *from_name = NULL;\n+\tchar *to_name = NULL;\n+\n+\tif (from) {\n+\t\tif (from->remote_status && !strncmp(from->remote_status, \"ref:\", 4))\n+\t\t\tfrom_name = from->remote_status + 4;\n+\t\telse\n+\t\t\tfrom_name = from->name;\n+\t}\n+\n+\tif (to) {\n+\t\tif (to->remote_status && !strncmp(to->remote_status, \"ref:\", 4))\n+\t\t\tto_name = to->remote_status + 4;\n+\t\telse\n+\t\t\tto_name = to->name;\n+\t}\n+\n \tif (porcelain) {\n \t\tif (from)\n-\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from_name, to_name);\n \t\telse\n-\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to_name);\n \t\tif (msg)\n \t\t\tfprintf(stdout, \"%s (%s)\\n\", summary, msg);\n \t\telse\n@@ -481,9 +498,9 @@ static void print_ref_status(char flag, const char *summary,\n \t\tfprintf(stderr, \" %s%c %-*s%s \", red, flag, summary_width,\n \t\t\tsummary, reset);\n \t\tif (from)\n-\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(to->name));\n+\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from_name), prettify_refname(to_name));\n \t\telse\n-\t\t\tfputs(prettify_refname(to->name), stderr);\n+\t\t\tfputs(prettify_refname(to_name), stderr);\n \t\tif (msg) {\n \t\t\tfputs(\" (\", stderr);\n \t\t\tfputs(msg, stderr);\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395294","messageId":"20200412133022.17590-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200407120813.25025-1-worldhello.net@gmail.com","subject":"[PATCH v10 1/8] transport: not report a non-head push as a branch","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-12T13:30:15Z","receivedAt":"2020-04-12T13:30:32Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing a new reference (not a head or tag), report it as a new\nreference instead of a new branch.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411-proc-receive-hook.sh               | 75 +++++++++++++++++++\n t/t5411/common-functions.sh                | 50 +++++++++++++\n t/t5411/common-test-cases.sh               | 43 +++++++++++\n t/t5412-proc-receive-hook-http-protocol.sh | 86 ++++++++++++++++++++++\n t/t5516-fetch-push.sh                      |  2 +-\n transport.c                                |  9 ++-\n 6 files changed, 261 insertions(+), 4 deletions(-)\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/common-test-cases.sh\n create mode 100755 t/t5412-proc-receive-hook-http-protocol.sh\n\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nnew file mode 100755\nindex 0000000000..ef289fe92a\n--- /dev/null\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -0,0 +1,75 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook'\n+\n+. ./test-lib.sh\n+\n+. \"$TEST_DIRECTORY\"/t5411/common-functions.sh\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  In addition to the common format method,\n+# we also replace the URL of different protocol for the upstream repository\n+# with a fixed pattern.\n+make_user_friendly_and_stable_output () {\n+\tmake_user_friendly_and_stable_output_common | sed \\\n+\t\t-e \"s#To ../upstream.git#To <URL/of/upstream.git>#\"\n+}\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+test_expect_success \"setup\" '\n+\tgit init --bare upstream.git &&\n+\tgit init workbench &&\n+\tcreate_commits_in workbench A B &&\n+\t(\n+\t\tcd workbench &&\n+\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\tgit config core.abbrev 7 &&\n+\t\tgit remote add origin ../upstream.git &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit tag -m \"v123\" v123 $A &&\n+\t\tgit push origin \\\n+\t\t\t$B:refs/heads/master \\\n+\t\t\t$A:refs/heads/next\n+\t) &&\n+\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t# setup pre-receive hook\n+\tcat >upstream.git/hooks/pre-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# pre-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"pre-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\t# setup post-receive hook\n+\tcat >upstream.git/hooks/post-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# post-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"post-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\tchmod a+x \\\n+\t\tupstream.git/hooks/pre-receive \\\n+\t\tupstream.git/hooks/post-receive &&\n+\n+\tupstream=upstream.git\n+'\n+\n+# Include test cases for both file and HTTP protocol\n+. \"$TEST_DIRECTORY\"/t5411/common-test-cases.sh\n+\n+test_done\ndiff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\nnew file mode 100644\nindex 0000000000..6e400c0625\n--- /dev/null\n+++ b/t/t5411/common-functions.sh\n@@ -0,0 +1,50 @@\n+# Create commits in <repo> and assign each commit's oid to shell variables\n+# given in the arguments (A, B, and C). E.g.:\n+#\n+#     create_commits_in <repo> A B C\n+#\n+# NOTE: Never calling this function from a subshell since variable\n+# assignments will disappear when subshell exits.\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{} --)\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  We can easily prepare the expect text\n+# without having to worry about future changes of the commit ID and spaces\n+# of the output.  We also replce single quotes with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect txt.\n+make_user_friendly_and_stable_output_common () {\n+\tsed \\\n+\t\t-e \"s/  *\\$//\" \\\n+\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/'/\\\"/g\" \\\n+\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n+\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n+\t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n+\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n+\t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n+\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\"\n+}\ndiff --git a/t/t5411/common-test-cases.sh b/t/t5411/common-test-cases.sh\nnew file mode 100644\nindex 0000000000..23655846e4\n--- /dev/null\n+++ b/t/t5411/common-test-cases.sh\n@@ -0,0 +1,43 @@\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"normal git-push command\" '\n+\tgit -C workbench push -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tHEAD:refs/heads/master \\\n+\t\tHEAD:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo <URL/of/upstream.git>\n+\t + <OID-B>...<OID-A> HEAD -> master (forced update)\n+\t - [deleted] next\n+\t * [new tag] v123 -> v123\n+\t * [new reference] HEAD -> refs/review/master/topic\n+\t * [new branch] HEAD -> a/b/c\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5412-proc-receive-hook-http-protocol.sh b/t/t5412-proc-receive-hook-http-protocol.sh\nnew file mode 100755\nindex 0000000000..e2446d4d32\n--- /dev/null\n+++ b/t/t5412-proc-receive-hook-http-protocol.sh\n@@ -0,0 +1,86 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook for HTTP protocol'\n+\n+. ./test-lib.sh\n+\n+ROOT_PATH=\"$PWD\"\n+. \"$TEST_DIRECTORY\"/lib-gpg.sh\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+. \"$TEST_DIRECTORY\"/lib-terminal.sh\n+start_httpd\n+\n+. \"$TEST_DIRECTORY\"/t5411/common-functions.sh\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  In addition to the common format method,\n+# we also replace the URL of different protocol for the upstream repository\n+# with a fixed pattern.\n+make_user_friendly_and_stable_output () {\n+\tmake_user_friendly_and_stable_output_common | sed \\\n+\t\t-e \"s#To http:.*/upstream.git#To <URL/of/upstream.git>#\"\n+}\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+test_expect_success \"setup\" '\n+\tgit init --bare upstream.git &&\n+\tgit -C upstream.git config http.receivepack true &&\n+\tgit init workbench &&\n+\tcreate_commits_in workbench A B &&\n+\t(\n+\t\tcd workbench &&\n+\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\tgit config core.abbrev 7 &&\n+\t\tgit remote add origin ../upstream.git &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit tag -m \"v123\" v123 $A &&\n+\t\tgit push origin \\\n+\t\t\t$B:refs/heads/master \\\n+\t\t\t$A:refs/heads/next\n+\t) &&\n+\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t# setup pre-receive hook\n+\tcat >upstream.git/hooks/pre-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# pre-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"pre-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\t# setup post-receive hook\n+\tcat >upstream.git/hooks/post-receive <<-\\EOF &&\n+\t#!/bin/sh\n+\n+\techo >&2 \"# post-receive hook\"\n+\n+\twhile read old new ref\n+\tdo\n+\t\techo >&2 \"post-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\tchmod a+x \\\n+\t\tupstream.git/hooks/pre-receive \\\n+\t\tupstream.git/hooks/post-receive &&\n+\n+\tupstream=\"$HTTPD_DOCUMENT_ROOT_PATH/upstream.git\" &&\n+\tmv upstream.git \"$upstream\" &&\n+\tgit -C workbench remote set-url origin $HTTPD_URL/smart/upstream.git\n+'\n+\n+setup_askpass_helper\n+\n+# Include test cases for both file and HTTP protocol\n+. \"$TEST_DIRECTORY\"/t5411/common-test-cases.sh\n+\n+test_done\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 9ff041a093..9e4b9313b5 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1039,7 +1039,7 @@ test_force_fetch_tag \"annotated tag\" \"-f -a -m'tag message'\"\n test_expect_success 'push --porcelain' '\n \tmk_empty testrepo &&\n \techo >.git/foo  \"To testrepo\" &&\n-\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new branch]\"  &&\n+\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new reference]\"  &&\n \techo >>.git/foo \"Done\" &&\n \tgit push >.git/bar --porcelain  testrepo refs/heads/master:refs/remotes/origin/master &&\n \t(\ndiff --git a/transport.c b/transport.c\nindex 1fdc7dac1a..272c0f4046 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -500,9 +500,12 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\t\t\t porcelain, summary_width);\n \telse if (is_null_oid(&ref->old_oid))\n \t\tprint_ref_status('*',\n-\t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n-\t\t\t\"[new branch]\"),\n-\t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t  ? \"[new tag]\"\n+\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t     ? \"[new branch]\"\n+\t\t\t\t     : \"[new reference]\")),\n+\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395295","messageId":"20200412133022.17590-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200407120813.25025-1-worldhello.net@gmail.com","subject":"[PATCH v10 0/8] New proc-receive hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-12T13:30:14Z","receivedAt":"2020-04-12T13:30:32Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\n\n## Changes since v9\n\n1. Minor refactors on patch 1/8, 2/8, 4/8, and 8/8.\n\n2. New patch 5/8: export function `parse_feature_value()`.\n\n3. Split patch 5/6 of reroll v9 into two patches (6/8 and 7/8),\n   one for the server-side and one for the client-side.\n   so can make a compatible test on old version of a Git client.\n\n4. Patch 6/8: Use null character to split extended status of the report\n   line, and add other key-value pairs for extension. For example:\n\n   When we push to a pseudo reference using the following command:\n\n       git push origin HEAD:refs/for/master/topic\n\n   Even if the push command updates an existing pull request (such as\n   refs/pull/123/head) instead of creating a new one, `git push`\n   always give the following report:\n\n       To <URL/of/upstream.git>\n         * [new reference]      HEAD -> refs/pull/123/head\n\n   After add more key-value pairs in the extended status, such as:\n   'old-oid=<...>', 'forced-update', we get a nicer status report like:\n\n       To upstream.git\n        + 1234567...7654321     HEAD -> refs/pull/124/head (forced update)\n\n\n## Range-diff v9...v10\n\n1:  ba6222899b ! 1:  a293077bcf transport: not report a non-head push as a branch\n    @@ t/t5411-proc-receive-hook.sh (new)\n     +\n     +# Format the output of git-push, git-show-ref and other commands to make a\n     +# user-friendly and stable text.  In addition to the common format method,\n    -+# we also replace URL of different protocol for the upstream repository to\n    -+# a fixed pattern.\n    ++# we also replace the URL of different protocol for the upstream repository\n    ++# with a fixed pattern.\n     +make_user_friendly_and_stable_output () {\n     +\tmake_user_friendly_and_stable_output_common | sed \\\n     +\t\t-e \"s#To ../upstream.git#To <URL/of/upstream.git>#\"\n    @@ t/t5411/common-functions.sh (new)\n     +\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n     +\t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n     +\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n    -+\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\"\n    ++\t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n    ++\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\"\n     +}\n     \n      ## t/t5411/common-test-cases.sh (new) ##\n    @@ t/t5411/common-test-cases.sh (new)\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n     +\tTo <URL/of/upstream.git>\n    -+\t + <OID>...<OID> HEAD -> master (forced update)\n    ++\t + <OID-B>...<OID-A> HEAD -> master (forced update)\n     +\t - [deleted] next\n     +\t * [new tag] v123 -> v123\n     +\t * [new reference] HEAD -> refs/review/master/topic\n    @@ t/t5412-proc-receive-hook-http-protocol.sh (new)\n     +\n     +# Format the output of git-push, git-show-ref and other commands to make a\n     +# user-friendly and stable text.  In addition to the common format method,\n    -+# we also replace URL of different protocol for the upstream repository to\n    -+# a fixed pattern.\n    ++# we also replace the URL of different protocol for the upstream repository\n    ++# with a fixed pattern.\n     +make_user_friendly_and_stable_output () {\n     +\tmake_user_friendly_and_stable_output_common | sed \\\n     +\t\t-e \"s#To http:.*/upstream.git#To <URL/of/upstream.git>#\"\n2:  195c5b0a0c ! 2:  915b9ef616 receive-pack: add new proc-receive hook\n    @@ Commit message\n         format.  In the following example, The letter \"S\" stands for\n         \"receive-pack\" and letter \"H\" stands for the hook.\n     \n    -        # Version and capabilities negotiation.\n    +        # Version and features negotiation.\n             S: PKT-LINE(version=1\\0push-options atomic...)\n             S: flush-pkt\n             H: PKT-LINE(version=1\\0push-options...)\n             H: flush-pkt\n     \n             # Send commands from server to the hook.\n    -        S: PKT-LINE(old-oid new-oid ref)\n    +        S: PKT-LINE(<old-oid> <new-oid> <ref>)\n             S: ... ...\n             S: flush-pkt\n    -        # Only if push-options have been negotiated.\n    +        # Send push-options only if the 'push-options' feature is enabled.\n             S: PKT-LINE(push-option)\n             S: ... ...\n             S: flush-pkt\n     \n             # Receive result from the hook.\n             # OK, run this command successfully.\n    -        H: PKT-LINE(old-oid new-oid ref ok)\n    +        H: PKT-LINE(<old-oid> <new-oid> <ref> ok)\n             # NO, I reject it.\n    -        H: PKT-LINE(old-oid new-oid ref ng reason)\n    -        # OK, but use an alternate reference. (in latter commit)\n    -        H: PKT-LINE(old-oid new-oid ref ok ref:alt-ref)\n    -        # It will fallthrough to receive-pack to execute. (in latter commit)\n    -        H: PKT-LINE(old-oid new-oid ref ft)\n    +        H: PKT-LINE(<old-oid> <new-oid> <ref> ng <reason>)\n    +        # Fall through, let 'receive-pack' to execute it.\n    +        H: PKT-LINE(<old-oid> <new-oid> <ref> ft)\n    +        # OK, but has an alternate reference.  The alternate reference name\n    +        # and other status are given in key=value pairs after the null\n    +        # character.\n    +        H: PKT-LINE(<old-oid> <new-oid> <ref> ok\\0ref=refs/pull/123/head\n    +                    forced-update)\n             H: ... ...\n             H: flush-pkt\n     \n    -    After receiving a command, the hook can create/update another alternate\n    -    reference.  For example, a command for a reference \"refs/for/master\" may\n    -    create a special reference, such as \"refs/pull/123/head\".  The alternate\n    -    reference can be returned from the result in an extensible format like\n    -    \"<old-oid> <new-oid> <reference> <status> [<message>]\".\n    +    After receiving a command, the hook will execute the command, and may\n    +    create/update different reference.  For example, a command for a pseudo\n    +    reference \"refs/for/master/topic\" may create/update different reference\n    +    such as \"refs/pull/123/head\".  The alternate reference name and other\n    +    status are given in key-value pairs as extended status of the report\n    +    line.\n     \n    -    The result will be stored in a command list, and \"receive-pack\" will use\n    -    the result to replace the commands that have specific `run_proc_receive`\n    -    field turned on.\n    +    The list of commands returned from \"proc-receive\" will replace the\n    +    relevant commands that are sent from user to \"receive-pack\", and\n    +    \"receive-pack\" will continue to run the \"execute_commands\" function and\n    +    other routines.  Finally, the result of the execution of these commands\n    +    will be reported to end user.\n     \n         Suggested-by: Junio C Hamano <gitster@pobox.com>\n         Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +static struct command *find_command_by_refname(const struct command *list,\n     +\t\t\t\t\t       const char *refname)\n     +{\n    -+\tfor ( ; list; list = list->next)\n    ++\tfor (; list; list = list->next)\n     +\t\tif (!strcmp(list->ref_name, refname))\n     +\t\t\treturn (struct command *)list;\n     +\treturn NULL;\n     +}\n     +\n    -+static int read_proc_receive_result(struct packet_reader *reader,\n    ++static int read_proc_receive_report(struct packet_reader *reader,\n     +\t\t\t\t    struct command *commands,\n     +\t\t\t\t    struct strbuf *errmsg)\n     +{\n    -+\tstruct command *hint;\n     +\tstruct command *cmd;\n    ++\tstruct command *hint = NULL;\n     +\tint code = 0;\n     +\n    -+\thint = NULL;\n     +\tfor (;;) {\n     +\t\tstruct object_id old_oid, new_oid;\n     +\t\tconst char *refname;\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t}\n     +\n     +\tfor (cmd = commands; cmd; cmd = cmd->next)\n    -+\t\tif (cmd->run_proc_receive &&\n    ++\t\tif (cmd->run_proc_receive && !cmd->error_string &&\n     +\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {\n     +\t\t    cmd->error_string = \"no report from proc-receive\";\n     +\t\t    code = -1;\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\n     +\t/* Send commands */\n     +\tfor (cmd = commands; cmd; cmd = cmd->next) {\n    -+\t\tchar *old_hex, *new_hex;\n    -+\n     +\t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)\n     +\t\t\tcontinue;\n    -+\t\told_hex = oid_to_hex(&cmd->old_oid);\n    -+\t\tnew_hex = oid_to_hex(&cmd->new_oid);\n     +\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n    -+\t\t\t\t old_hex, new_hex, cmd->ref_name);\n    ++\t\t\t\t oid_to_hex(&cmd->old_oid),\n    ++\t\t\t\t oid_to_hex(&cmd->new_oid),\n    ++\t\t\t\t cmd->ref_name);\n     +\t}\n     +\tpacket_flush(proc.in);\n     +\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t}\n     +\n     +\t/* Read result from proc-receive */\n    -+\tcode = read_proc_receive_result(&reader, commands, &errmsg);\n    ++\tcode = read_proc_receive_report(&reader, commands, &errmsg);\n     +\n     +cleanup:\n     +\tclose(proc.in);\n    @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n      \tfree(head_name_to_free);\n      \thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, NULL, NULL);\n      \n    -+\tif (run_proc_receive) {\n    -+\t\tint code;\n    -+\n    -+\t\tcode = run_proc_receive_hook(commands, push_options);\n    -+\t\tif (code) {\n    -+\t\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n    -+\t\t\t\tif (!cmd->error_string  && (cmd->run_proc_receive || use_atomic))\n    -+\t\t\t\t\tcmd->error_string = \"fail to run proc-receive hook\";\n    -+\t\t\t}\n    -+\t\t}\n    -+\t}\n    ++\tif (run_proc_receive &&\n    ++\t    run_proc_receive_hook(commands, push_options))\n    ++\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n    ++\t\t\tif (!cmd->error_string  && (cmd->run_proc_receive || use_atomic))\n    ++\t\t\t\tcmd->error_string = \"fail to run proc-receive hook\";\n     +\n      \tif (use_atomic)\n      \t\texecute_commands_atomic(commands, si);\n    @@ t/helper/test-proc-receive.c (new)\n     +\t\tconst char *refname;\n     +\t\tconst char *p;\n     +\n    -+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n    ++\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n     +\t\t\tbreak;\n    -+\t\t}\n     +\n     +\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n     +\t\t    *p++ != ' ' ||\n    @@ t/helper/test-proc-receive.c (new)\n     +int cmd__proc_receive(int argc, const char **argv)\n     +{\n     +\tstruct packet_reader reader;\n    -+\tstruct command *commands;\n    ++\tstruct command *commands = NULL;\n     +\tstruct string_list push_options = STRING_LIST_INIT_DUP;\n     +\tstruct string_list_item *item;\n     +\tstruct option options[] = {\n    @@ t/helper/test-proc-receive.c (new)\n     +\t\t\t\tuse_atomic? \" atomic\": \"\",\n     +\t\t\t\tuse_push_options ? \" push_options\": \"\");\n     +\n    -+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n    -+\t\t\tchar *old_hex, *new_hex;\n    -+\n    -+\t\t\told_hex = oid_to_hex(&cmd->old_oid);\n    -+\t\t\tnew_hex = oid_to_hex(&cmd->new_oid);\n    ++\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n     +\t\t\tfprintf(stderr, \"proc-receive< %s %s %s\\n\",\n    -+\t\t\t\told_hex, new_hex, cmd->ref_name);\n    -+\t\t}\n    ++\t\t\t\toid_to_hex(&cmd->old_oid),\n    ++\t\t\t\toid_to_hex(&cmd->new_oid),\n    ++\t\t\t\tcmd->ref_name);\n     +\n    -+\t\tif (push_options.nr > 0) {\n    ++\t\tif (push_options.nr > 0)\n     +\t\t\tfor_each_string_list_item(item, &push_options)\n     +\t\t\t\tfprintf(stderr, \"proc-receive< %s\\n\", item->string);\n    -+\t\t}\n     +\n    -+\t\tif (returns.nr) {\n    ++\t\tif (returns.nr)\n     +\t\t\tfor_each_string_list_item(item, &returns)\n     +\t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n    -+\t\t}\n     +\t}\n     +\n    -+\tif (returns.nr) {\n    ++\tif (returns.nr)\n     +\t\tfor_each_string_list_item(item, &returns)\n     +\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n    -+\t}\n     +\tpacket_flush(1);\n     +\tsigchain_pop(SIGPIPE);\n     +\n    @@ t/t5411/common-functions.sh: create_commits_in () {\n      \tsed \\\n      \t\t-e \"s/  *\\$//\" \\\n     @@ t/t5411/common-functions.sh: make_user_friendly_and_stable_output_common () {\n    - \t\t-e \"s/$B/<COMMIT-B>/g\" \\\n      \t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n      \t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n    --\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\"\n    -+\t\t-e \"s/[0-9a-f]\\{7,\\}/<OID>/g\" \\\n    + \t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n    +-\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\"\n    ++\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\" \\\n     +\t\t-e \"/^error: / d\"\n      }\n     \n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n     +\tTo <URL/of/upstream.git>\n    -+\t <OID>..<OID> <COMMIT-B> -> master\n    ++\t <OID-A>..<OID-B> <COMMIT-B> -> master\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n     +\tEOF\n     +\ttest_cmp expect actual &&\n3:  cde556e9c7 = 3:  16257616ec refs.c: refactor to reuse ref_is_hidden()\n4:  3200327695 ! 4:  cd49b57c17 receive-pack: new config receive.procReceiveRefs\n    @@ Commit message\n         Add a new multi-valued config variable \"receive.procReceiveRefs\"\n         for `receive-pack` command, like the follows:\n     \n    -        git config --system --add receive.procReceiveRefs refs/for/\n    -        git config --system --add receive.procReceiveRefs refs/drafts/\n    +        git config --system --add receive.procReceiveRefs refs/for\n    +        git config --system --add receive.procReceiveRefs refs/drafts\n     \n         If the specific prefix strings match the reference names of the commands\n    -    which are sent by git client to `receive-pack`, these commands will be\n    +    which are sent from git client to `receive-pack`, these commands will be\n         executed by an external hook (named \"proc-receive\"), instead of the\n         internal `execute_commands` function.\n     \n    -    For example, if it is set to \"refs/for/\", pushing to a reference such as\n    +    For example, if it is set to \"refs/for\", pushing to a reference such as\n         \"refs/for/master\" will not create or update reference \"refs/for/master\",\n    -    but may create or update a pull request directly by running the external\n    -    hook.\n    +    but may create or update a pull request directly by running the hook\n    +    \"proc-receive\".\n     \n         Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n     \n    @@ Documentation/config/receive.txt: receive.hideRefs::\n     +receive.procReceiveRefs::\n     +\tThis is a multi-valued variable that defines reference prefixes\n     +\tto match the commands in `receive-pack`.  Commands matching the\n    -+\tprefixes will be executed by an external hooks \"proc-receive\",\n    ++\tprefixes will be executed by an external hook \"proc-receive\",\n     +\tinstead of the internal `execute_commands` function.  If this\n     +\tvariable is not defined, the \"proc-receive\" hook will never be\n     +\tused, and all commands will be executed by the internal\n    @@ Documentation/config/receive.txt: receive.hideRefs::\n     +\tFor example, if this variable is set to \"refs/for\", pushing to\n     +\treference such as \"refs/for/master\" will not create or update a\n     +\treference named \"refs/for/master\", but may create or update a\n    -+\tpull request directly by running an external hook.\n    ++\tpull request directly by running the hook \"proc-receive\".\n     +\n      receive.updateServerInfo::\n      \tIf set to true, git-receive-pack will run git-update-server-info\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"push with options\" '\n     +\ttest-tool proc-receive -v \\\n     +\t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n     +\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n    -+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n    ++\t\t-r \"$B $A refs/for/master/topic ok\"\n     +\tEOF\n     +\tchmod a+x \"$upstream/hooks/proc-receive\"\n     +'\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"push with options\" '\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n    ++\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/master/topic ok\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n     +\t * [new reference] HEAD -> refs/for/next/topic\n     +\t * [new reference] HEAD -> refs/review/a/b/c/topic\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"push with options\" '\n     +\n     +\ttest-tool proc-receive -v \\\n     +\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n    -+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n    ++\t\t-r \"$A $B refs/for/master/topic ok\"\n     +\tEOF\n     +\tchmod a+x \"$upstream/hooks/proc-receive\"\n     +'\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"push with options\" '\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n    ++\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/for/master/topic ok\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n     +\t * [new branch] HEAD -> bar\n     +\t * [new branch] HEAD -> baz\n5:  5f2ab02b01 < -:  ---------- receive-pack: refactor report for proc-receive\n6:  b7d7175d89 < -:  ---------- doc: add documentation for the proc-receive hook\n-:  ---------- > 5:  75b178fc19 connect: export parse_feature_value()\n-:  ---------- > 6:  860860536e receive-pack: extension for server-side report\n-:  ---------- > 7:  d1677b61d1 send-pack: extension for client-side status report\n-:  ---------- > 8:  41159c2ef2 doc: add documentation for the proc-receive hook\n\n\nJiang Xin (8):\n  transport: not report a non-head push as a branch\n  receive-pack: add new proc-receive hook\n  refs.c: refactor to reuse ref_is_hidden()\n  receive-pack: new config receive.procReceiveRefs\n  connect: export parse_feature_value()\n  receive-pack: extension for server-side report\n  send-pack: extension for client-side status report\n  doc: add documentation for the proc-receive hook\n\n Documentation/config/receive.txt           |  14 +\n Documentation/githooks.txt                 |  58 ++\n Makefile                                   |   1 +\n builtin/receive-pack.c                     | 334 ++++++++-\n connect.c                                  |   3 +-\n connect.h                                  |   1 +\n refs.c                                     |  11 +-\n refs.h                                     |   1 +\n remote.c                                   |  28 +\n remote.h                                   |   3 +\n send-pack.c                                |  13 +-\n t/helper/test-proc-receive.c               | 178 +++++\n t/helper/test-tool.c                       |   1 +\n t/helper/test-tool.h                       |   1 +\n t/t5411-proc-receive-hook.sh               |  75 ++\n t/t5411/common-functions.sh                |  54 ++\n t/t5411/common-test-cases.sh               | 827 +++++++++++++++++++++\n t/t5412-proc-receive-hook-http-protocol.sh |  86 +++\n t/t5516-fetch-push.sh                      |   2 +-\n transport-helper.c                         |  66 +-\n transport.c                                |  42 +-\n 21 files changed, 1745 insertions(+), 54 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/common-test-cases.sh\n create mode 100755 t/t5412-proc-receive-hook-http-protocol.sh\n\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395296","messageId":"20200412133022.17590-4-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200407120813.25025-1-worldhello.net@gmail.com","subject":"[PATCH v10 3/8] refs.c: refactor to reuse ref_is_hidden()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-12T13:30:17Z","receivedAt":"2020-04-12T13:30:35Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd new function `ref_is_matched()` to reuse `ref_is_hidden()`. Will use\nthis function for `receive-pack` to check commands with specific\nprefixes.\n\nTest case t5512 covered this change.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n refs.c | 11 ++++++++---\n refs.h |  1 +\n 2 files changed, 9 insertions(+), 3 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 1ab0bb54d3..229159ea1a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1389,13 +1389,18 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti\n }\n \n int ref_is_hidden(const char *refname, const char *refname_full)\n+{\n+\treturn ref_is_matched(hide_refs, refname, refname_full);\n+}\n+\n+int ref_is_matched(struct string_list *match_refs, const char *refname, const char *refname_full)\n {\n \tint i;\n \n-\tif (!hide_refs)\n+\tif (!match_refs)\n \t\treturn 0;\n-\tfor (i = hide_refs->nr - 1; i >= 0; i--) {\n-\t\tconst char *match = hide_refs->items[i].string;\n+\tfor (i = match_refs->nr - 1; i >= 0; i--) {\n+\t\tconst char *match = match_refs->items[i].string;\n \t\tconst char *subject;\n \t\tint neg = 0;\n \t\tconst char *p;\ndiff --git a/refs.h b/refs.h\nindex 545029c6d8..a2ea043f7f 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -739,6 +739,7 @@ int parse_hide_refs_config(const char *var, const char *value, const char *);\n  * parameter always points to the full ref name.\n  */\n int ref_is_hidden(const char *, const char *);\n+int ref_is_matched(struct string_list *, const char *, const char *);\n \n enum ref_type {\n \tREF_TYPE_PER_WORKTREE,\t  /* refs inside refs/ but not shared       */\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395298","messageId":"20200412133022.17590-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200407120813.25025-1-worldhello.net@gmail.com","subject":"[PATCH v10 2/8] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-12T13:30:16Z","receivedAt":"2020-04-12T13:30:35Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nGit calls an internal `execute_commands` function to handle commands\nsent from client to `git-receive-pack`.  Regardless of what references\nthe user pushes, git creates or updates the corresponding references if\nthe user has write-permission.  A contributor who has no\nwrite-permission, cannot push to the repository directly.  So, the\ncontributor has to write commits to an alternate location, and sends\npull request by emails or by other ways.  We call this workflow as a\ndistributed workflow.\n\nIt would be more convenient to work in a centralized workflow like what\nGerrit provided for some cases.  For example, a read-only user who\ncannot push to a branch directly can run the following `git push`\ncommand to push commits to a pseudo reference (has a prefix \"refs/for/\",\nnot \"refs/heads/\") to create a code review.\n\n    git push origin \\\n        HEAD:refs/for/<branch-name>/<session>\n\nThe `<branch-name>` in the above example can be as simple as \"master\",\nor a more complicated branch name like \"foo/bar\".  The `<session>` in\nthe above example command can be the local branch name of the client\nside, such as \"my/topic\".\n\nWe cannot implement a centralized workflow elegantly by using\n\"pre-receive\" + \"post-receive\", because Git will call the internal\nfunction \"execute_commands\" to create references (even the special\npseudo reference) between these two hooks.  Even though we can delete\nthe temporarily created pseudo reference via the \"post-receive\" hook,\nhaving a temporary reference is not safe for concurrent pushes.\n\nSo, add a filter and a new handler to support this kind of workflow.\nThe filter will check the prefix of the reference name, and if the\ncommand has a special reference name, the filter will turn a specific\nfield (`run_proc_receive`) on for the command.  Commands with this filed\nturned on will be executed by a new handler (an hook named\n\"proc-receive\") instead of the internal `execute_commands` function.\nWe can use this \"proc-receive\" command to create pull requests or send\nemails for code review.\n\nSuggested by Junio, this \"proc-receive\" hook reads the commands,\npush-options (optional), and send result using a protocol in pkt-line\nformat.  In the following example, The letter \"S\" stands for\n\"receive-pack\" and letter \"H\" stands for the hook.\n\n    # Version and features negotiation.\n    S: PKT-LINE(version=1\\0push-options atomic...)\n    S: flush-pkt\n    H: PKT-LINE(version=1\\0push-options...)\n    H: flush-pkt\n\n    # Send commands from server to the hook.\n    S: PKT-LINE(<old-oid> <new-oid> <ref>)\n    S: ... ...\n    S: flush-pkt\n    # Send push-options only if the 'push-options' feature is enabled.\n    S: PKT-LINE(push-option)\n    S: ... ...\n    S: flush-pkt\n\n    # Receive result from the hook.\n    # OK, run this command successfully.\n    H: PKT-LINE(<old-oid> <new-oid> <ref> ok)\n    # NO, I reject it.\n    H: PKT-LINE(<old-oid> <new-oid> <ref> ng <reason>)\n    # Fall through, let 'receive-pack' to execute it.\n    H: PKT-LINE(<old-oid> <new-oid> <ref> ft)\n    # OK, but has an alternate reference.  The alternate reference name\n    # and other status are given in key=value pairs after the null\n    # character.\n    H: PKT-LINE(<old-oid> <new-oid> <ref> ok\\0ref=refs/pull/123/head\n                forced-update)\n    H: ... ...\n    H: flush-pkt\n\nAfter receiving a command, the hook will execute the command, and may\ncreate/update different reference.  For example, a command for a pseudo\nreference \"refs/for/master/topic\" may create/update different reference\nsuch as \"refs/pull/123/head\".  The alternate reference name and other\nstatus are given in key-value pairs as extended status of the report\nline.\n\nThe list of commands returned from \"proc-receive\" will replace the\nrelevant commands that are sent from user to \"receive-pack\", and\n\"receive-pack\" will continue to run the \"execute_commands\" function and\nother routines.  Finally, the result of the execution of these commands\nwill be reported to end user.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Makefile                     |   1 +\n builtin/receive-pack.c       | 260 +++++++++++++++-\n t/helper/test-proc-receive.c | 168 ++++++++++\n t/helper/test-tool.c         |   1 +\n t/helper/test-tool.h         |   1 +\n t/t5411/common-functions.sh  |  10 +-\n t/t5411/common-test-cases.sh | 576 +++++++++++++++++++++++++++++++++++\n 7 files changed, 1011 insertions(+), 6 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n\ndiff --git a/Makefile b/Makefile\nindex ef1ff2228f..1c52c280ce 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -725,6 +725,7 @@ TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\n+TEST_BUILTINS_OBJS += test-proc-receive.o\n TEST_BUILTINS_OBJS += test-progress.o\n TEST_BUILTINS_OBJS += test-reach.o\n TEST_BUILTINS_OBJS += test-read-cache.o\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 2cc18bbffd..1a57c5bb3b 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -308,11 +308,14 @@ static void write_head_info(void)\n \tpacket_flush(1);\n }\n \n+#define RUN_PROC_RECEIVE_SCHEDULED\t1\n+#define RUN_PROC_RECEIVE_RETURNED\t2\n struct command {\n \tstruct command *next;\n \tconst char *error_string;\n \tunsigned int skip_update:1,\n-\t\t     did_not_exist:1;\n+\t\t     did_not_exist:1,\n+\t\t     run_proc_receive:2;\n \tint index;\n \tstruct object_id old_oid;\n \tstruct object_id new_oid;\n@@ -817,6 +820,236 @@ static int run_update_hook(struct command *cmd)\n \treturn finish_command(&proc);\n }\n \n+static struct command *find_command_by_refname(const struct command *list,\n+\t\t\t\t\t       const char *refname)\n+{\n+\tfor (; list; list = list->next)\n+\t\tif (!strcmp(list->ref_name, refname))\n+\t\t\treturn (struct command *)list;\n+\treturn NULL;\n+}\n+\n+static int read_proc_receive_report(struct packet_reader *reader,\n+\t\t\t\t    struct command *commands,\n+\t\t\t\t    struct strbuf *errmsg)\n+{\n+\tstruct command *cmd;\n+\tstruct command *hint = NULL;\n+\tint code = 0;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\t\tchar *status;\n+\t\tchar *msg = NULL;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ') {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\\n\",\n+\t\t\t\t    reader->line);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\trefname = p;\n+\t\tstatus = strchr(p, ' ');\n+\t\tif (!status) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\\n\",\n+\t\t\t\t    reader->line);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\t*status++ = '\\0';\n+\t\tif (strlen(status) > 2 && *(status + 2) == ' ') {\n+\t\t\tmsg = status + 2;\n+\t\t\t*msg++ = '\\0';\n+\t\t}\n+\t\tif (strlen(status) != 2) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n+\t\t\t\t    status, reader->line);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/* first try searching at our hint, falling back to all refs */\n+\t\tif (hint)\n+\t\t\thint = find_command_by_refname(hint, refname);\n+\t\tif (!hint)\n+\t\t\thint = find_command_by_refname(commands, refname);\n+\t\tif (!hint) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unknown ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!hint->run_proc_receive) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on ref of builtin command: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(status, \"ng\")) {\n+\t\t\tif (msg)\n+\t\t\t\thint->error_string = xstrdup(msg);\n+\t\t\telse\n+\t\t\t\thint->error_string = \"failed\";\n+\t\t\tcode = -1;\n+\t\t} else if (strcmp(\"ok\", status)) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n+\t\t\t\t    status, reader->line);\n+\t\t\tcode = -1;\n+\t\t\t/* Skip marking it as RUN_PROC_RECEIVE_RETURNED */\n+\t\t\tcontinue;\n+\t\t}\n+\t\toidcpy(&hint->old_oid, &old_oid);\n+\t\toidcpy(&hint->new_oid, &new_oid);\n+\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n+\t}\n+\n+\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\tif (cmd->run_proc_receive && !cmd->error_string &&\n+\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {\n+\t\t    cmd->error_string = \"no report from proc-receive\";\n+\t\t    code = -1;\n+\t\t}\n+\n+\treturn code;\n+}\n+\n+static int run_proc_receive_hook(struct command *commands,\n+\t\t\t\t const struct string_list *push_options)\n+{\n+\tstruct child_process proc = CHILD_PROCESS_INIT;\n+\tstruct async muxer;\n+\tstruct command *cmd;\n+\tconst char *argv[2];\n+\tstruct packet_reader reader;\n+\tstruct strbuf cap = STRBUF_INIT;\n+\tstruct strbuf errmsg = STRBUF_INIT;\n+\tint pr_use_push_options = 0;\n+\tint version = 0;\n+\tint code;\n+\n+\targv[0] = find_hook(\"proc-receive\");\n+\tif (!argv[0]) {\n+\t\trp_error(\"cannot find hook 'proc-receive'\");\n+\t\treturn -1;\n+\t}\n+\targv[1] = NULL;\n+\n+\tproc.argv = argv;\n+\tproc.in = -1;\n+\tproc.out = -1;\n+\tproc.trace2_hook_name = \"proc-receive\";\n+\n+\tif (use_sideband) {\n+\t\tmemset(&muxer, 0, sizeof(muxer));\n+\t\tmuxer.proc = copy_to_sideband;\n+\t\tmuxer.in = -1;\n+\t\tcode = start_async(&muxer);\n+\t\tif (code)\n+\t\t\treturn code;\n+\t\tproc.err = muxer.in;\n+\t} else {\n+\t\tproc.err = 0;\n+\t}\n+\n+\tcode = start_command(&proc);\n+\tif (code) {\n+\t\tif (use_sideband)\n+\t\t\tfinish_async(&muxer);\n+\t\treturn code;\n+\t}\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\n+\t/* Version negotiaton */\n+\tpacket_reader_init(&reader, proc.out, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\tif (use_atomic)\n+\t\tstrbuf_addstr(&cap, \" atomic\");\n+\tif (use_push_options)\n+\t\tstrbuf_addstr(&cap, \" push-options\");\n+\tif (cap.len) {\n+\t\tpacket_write_fmt(proc.in, \"version=1%c%s\\n\", '\\0', cap.buf + 1);\n+\t\tstrbuf_release(&cap);\n+\t} else {\n+\t\tpacket_write_fmt(proc.in, \"version=1\\n\");\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader.pktlen > 8 && starts_with(reader.line, \"version=\")) {\n+\t\t\tversion = atoi(reader.line + 8);\n+\t\t\tlinelen = strlen(reader.line);\n+\t\t\tif (linelen < reader.pktlen) {\n+\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tpr_use_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (version != 1) {\n+\t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n+\t\t\t    version);\n+\t\tcode = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\t/* Send commands */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)\n+\t\t\tcontinue;\n+\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n+\t\t\t\t oid_to_hex(&cmd->old_oid),\n+\t\t\t\t oid_to_hex(&cmd->new_oid),\n+\t\t\t\t cmd->ref_name);\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\t/* Send push options */\n+\tif (pr_use_push_options) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tfor_each_string_list_item(item, push_options)\n+\t\t\tpacket_write_fmt(proc.in, \"%s\", item->string);\n+\t\tpacket_flush(proc.in);\n+\t}\n+\n+\t/* Read result from proc-receive */\n+\tcode = read_proc_receive_report(&reader, commands, &errmsg);\n+\n+cleanup:\n+\tclose(proc.in);\n+\tclose(proc.out);\n+\tif (use_sideband)\n+\t\tfinish_async(&muxer);\n+\tif (finish_command(&proc))\n+\t\tcode = -1;\n+\tif (errmsg.len >0) {\n+\t\tchar *p = errmsg.buf;\n+\n+\t\tp += errmsg.len - 1;\n+\t\tif (*p == '\\n')\n+\t\t\t*p = '\\0';\n+\t\trp_error(\"%s\", errmsg.buf);\n+\t\tstrbuf_release(&errmsg);\n+\t}\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn code;\n+}\n+\n static char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n@@ -1392,7 +1625,7 @@ static void execute_commands_non_atomic(struct command *commands,\n \tstruct strbuf err = STRBUF_INIT;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\ttransaction = ref_transaction_begin(&err);\n@@ -1432,7 +1665,7 @@ static void execute_commands_atomic(struct command *commands,\n \t}\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\tcmd->error_string = update(cmd, si);\n@@ -1468,6 +1701,7 @@ static void execute_commands(struct command *commands,\n \tstruct iterate_data data;\n \tstruct async muxer;\n \tint err_fd = 0;\n+\tint run_proc_receive = 0;\n \n \tif (unpacker_error) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n@@ -1497,6 +1731,20 @@ static void execute_commands(struct command *commands,\n \n \treject_updates_to_hidden(commands);\n \n+\t/* Try to find commands that have special prefix in their reference names,\n+\t * and mark them to run an external \"proc-receive\" hook later.\n+\t */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!should_process_cmd(cmd))\n+\t\t\tcontinue;\n+\n+\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n+\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n+\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\trun_proc_receive = 1;\n+\t\t}\n+\t}\n+\n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n \t\t\tif (!cmd->error_string)\n@@ -1523,6 +1771,12 @@ static void execute_commands(struct command *commands,\n \tfree(head_name_to_free);\n \thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, NULL, NULL);\n \n+\tif (run_proc_receive &&\n+\t    run_proc_receive_hook(commands, push_options))\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tif (!cmd->error_string  && (cmd->run_proc_receive || use_atomic))\n+\t\t\t\tcmd->error_string = \"fail to run proc-receive hook\";\n+\n \tif (use_atomic)\n \t\texecute_commands_atomic(commands, si);\n \telse\ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nnew file mode 100644\nindex 0000000000..8fb143d692\n--- /dev/null\n+++ b/t/helper/test-proc-receive.c\n@@ -0,0 +1,168 @@\n+#include \"cache.h\"\n+#include \"connect.h\"\n+#include \"parse-options.h\"\n+#include \"pkt-line.h\"\n+#include \"sigchain.h\"\n+#include \"string-list.h\"\n+#include \"test-tool.h\"\n+\n+static const char *proc_receive_usage[] = {\n+\t\"test-tool proc-receive [<options>...]\",\n+\tNULL\n+};\n+\n+static int version = 1;\n+static int verbose = 0;\n+static int no_push_options = 0;\n+static int use_atomic = 0;\n+static int use_push_options = 0;\n+static struct string_list returns = STRING_LIST_INIT_NODUP;\n+\n+struct command {\n+\tstruct command *next;\n+\tconst char *error_string;\n+\tunsigned int skip_update:1,\n+\t\t     did_not_exist:1;\n+\tint index;\n+\tstruct object_id old_oid;\n+\tstruct object_id new_oid;\n+\tchar ref_name[FLEX_ARRAY]; /* more */\n+};\n+\n+static void proc_receive_verison(struct packet_reader *reader) {\n+\tint server_version = 0;\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader->pktlen > 8 && starts_with(reader->line, \"version=\")) {\n+\t\t\tserver_version = atoi(reader->line+8);\n+\t\t\tlinelen = strlen(reader->line);\n+\t\t\tif (linelen < reader->pktlen) {\n+\t\t\t\tconst char *feature_list = reader->line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"atomic\"))\n+\t\t\t\t\tuse_atomic= 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tuse_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (server_version != 1)\n+\t\tdie(\"bad protocol version: %d\", server_version);\n+\n+\tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n+\t\t\t version, '\\0',\n+\t\t\t use_push_options && !no_push_options ? \"push-options\": \"\");\n+\tpacket_flush(1);\n+}\n+\n+static void proc_receive_read_commands(struct packet_reader *reader,\n+\t\t\t\t       struct command **commands)\n+{\n+\tstruct command **tail = commands;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tstruct command *cmd;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ')\n+\t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n+\t\t\t    reader->line);\n+\t\trefname = p;\n+\t\tFLEX_ALLOC_STR(cmd, ref_name, refname);\n+\t\toidcpy(&cmd->old_oid, &old_oid);\n+\t\toidcpy(&cmd->new_oid, &new_oid);\n+\n+\t\t*tail = cmd;\n+\t\ttail = &cmd->next;\n+\t}\n+}\n+\n+static void proc_receive_read_push_options(struct packet_reader *reader,\n+\t\t\t\t\t   struct string_list *options)\n+{\n+\n+\tif (no_push_options || !use_push_options)\n+\t       return;\n+\n+\twhile (1) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tstring_list_append(options, reader->line);\n+\t}\n+}\n+\n+int cmd__proc_receive(int argc, const char **argv)\n+{\n+\tstruct packet_reader reader;\n+\tstruct command *commands = NULL;\n+\tstruct string_list push_options = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"no-push-options\", &no_push_options,\n+\t\t\t \"disable push options\"),\n+\t\tOPT_STRING_LIST('r', \"return\", &returns, \"old/new/ref/status/msg\",\n+\t\t\t\t\"return of results\"),\n+\t\tOPT__VERBOSE(&verbose, \"be verbose\"),\n+\t\tOPT_INTEGER('V', \"version\", &version,\n+\t\t\t    \"use this protocol version number\"),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, \"test-tools\", options, proc_receive_usage, 0);\n+\tif (argc > 0)\n+\t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n+\n+\tpacket_reader_init(&reader, 0, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\tproc_receive_verison(&reader);\n+\tproc_receive_read_commands(&reader, &commands);\n+\tproc_receive_read_push_options(&reader, &push_options);\n+\n+\tif (verbose) {\n+\t\tstruct command *cmd;\n+\n+\t\tif (use_push_options || use_atomic)\n+\t\t\tfprintf(stderr, \"proc-receive:%s%s\\n\",\n+\t\t\t\tuse_atomic? \" atomic\": \"\",\n+\t\t\t\tuse_push_options ? \" push_options\": \"\");\n+\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tfprintf(stderr, \"proc-receive< %s %s %s\\n\",\n+\t\t\t\toid_to_hex(&cmd->old_oid),\n+\t\t\t\toid_to_hex(&cmd->new_oid),\n+\t\t\t\tcmd->ref_name);\n+\n+\t\tif (push_options.nr > 0)\n+\t\t\tfor_each_string_list_item(item, &push_options)\n+\t\t\t\tfprintf(stderr, \"proc-receive< %s\\n\", item->string);\n+\n+\t\tif (returns.nr)\n+\t\t\tfor_each_string_list_item(item, &returns)\n+\t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n+\t}\n+\n+\tif (returns.nr)\n+\t\tfor_each_string_list_item(item, &returns)\n+\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n+\tpacket_flush(1);\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 31eedcd241..f865433a61 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -44,6 +44,7 @@ static struct test_cmd cmds[] = {\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\n+\t{ \"proc-receive\", cmd__proc_receive},\n \t{ \"progress\", cmd__progress },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 4eb5e6609e..059008cf22 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -34,6 +34,7 @@ int cmd__parse_pathspec_file(int argc, const char** argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\n+int cmd__proc_receive(int argc, const char **argv);\n int cmd__progress(int argc, const char **argv);\n int cmd__reach(int argc, const char **argv);\n int cmd__read_cache(int argc, const char **argv);\ndiff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\nindex 6e400c0625..ef3913bbb5 100644\n--- a/t/t5411/common-functions.sh\n+++ b/t/t5411/common-functions.sh\n@@ -34,8 +34,11 @@ create_commits_in () {\n # Format the output of git-push, git-show-ref and other commands to make a\n # user-friendly and stable text.  We can easily prepare the expect text\n # without having to worry about future changes of the commit ID and spaces\n-# of the output.  We also replce single quotes with double quotes, because\n-# it is boring to prepare unquoted single quotes in expect txt.\n+# of the output.  Single quotes are replaced with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect txt.  We also\n+# remove some locale error messages, which break test if we turn on\n+# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n+# on plumbing commands.\n make_user_friendly_and_stable_output_common () {\n \tsed \\\n \t\t-e \"s/  *\\$//\" \\\n@@ -46,5 +49,6 @@ make_user_friendly_and_stable_output_common () {\n \t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n \t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n \t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n-\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\"\n+\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\" \\\n+\t\t-e \"/^error: / d\"\n }\ndiff --git a/t/t5411/common-test-cases.sh b/t/t5411/common-test-cases.sh\nindex 23655846e4..8e30c8f31e 100644\n--- a/t/t5411/common-test-cases.sh\n+++ b/t/t5411/common-test-cases.sh\n@@ -41,3 +41,579 @@ test_expect_success \"normal git-push command\" '\n \tEOF\n \ttest_cmp expect actual\n '\n+\n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"no proc-receive hook, fail to push special ref\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n+test_expect_success \"no proc-receive hook, fail all for atomic push\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\t$B:master \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] <COMMIT-B> -> master (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad version)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive bad protocol: unknown version\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\t# Check status report for git-push\n+\tsed -n \\\n+\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n+\t\t-e \"/^ / { s/   */ /g; p; }\" \\\n+\t\t<actual >actual-report &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual-report &&\n+\n+\t# Check error message from \"receive-pack\", but ignore unstable fatal error\n+\t# message (\"remote: fatal: the remote end hung up unexpectedly\") which\n+\t# is different from the remote HTTP server with different locale settings.\n+\tsed -n -e \"/^remote: error:/ { s/   */ /g; p; }\" \\\n+\t\t<actual >actual-error &&\n+\tcat >expect <<-EOF &&\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tEOF\n+\ttest_cmp expect actual-error &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive bad protocol: no report\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad oid)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"bad-id new-id ref ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: bad oid\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> bad-id new-id ref ok\n+\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"bad-id new-id ref ok\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: no status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic xx msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: unknown status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic xx msg\n+\tremote: error: proc-receive has bad status \"xx\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad status)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic bad status\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive bad protocol: bad status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic bad status\n+\tremote: error: proc-receive has bad status \"bad status\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (no message)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (has message)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng error msg\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (report status on builtin command)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/heads/master ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: warning on report for builtin command\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/heads/master ok\n+\tremote: error: proc-receive reported status on ref of builtin command: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\n+\n+test_expect_success \"setup proc-receive hook (ok)\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: no report from proc-receive\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (no report from proc-receive)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"not support push options\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n+\t\tactual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"push with options\" '\n+\tgit -C workbench push \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395297","messageId":"20200412133022.17590-7-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200407120813.25025-1-worldhello.net@gmail.com","subject":"[PATCH v10 6/8] receive-pack: extension for server-side report","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-12T13:30:20Z","receivedAt":"2020-04-12T13:30:37Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nEach command for the \"proc-receive\" hook may point to a pseudo-reference\nand always has a zero-old as its old-oid.  But the \"proc-receive\" hook\nmay update an alternate reference and the reference may exist already\n(has a non-zero old-oid).  And we may want certain commands to bypass\nthe \"proc-receive\" hook and let \"receive-pack\" handle them.\n\nIn order to report proper reference name, old-oid, and let certain\ncommands fall through, an extensible reporting method for \"proc-receive\"\nand \"receive-pack\" is introduced.\n\nThe \"proc-receive\" hook can report its results in the following format:\n\n    # OK, run this command successfully.\n    PKT-LINE(<old-oid> <new-oid> <ref> ok)\n\n    # NO, I reject it.\n    PKT-LINE(<old-oid> <new-oid> <ref> ng <reason>)\n\n    # Fall through, let \"receive-pack\" to execute it.\n    PKT-LINE(<old-oid> <new-oid> <ref> ft)\n\n    # OK, but has an alternate reference.  The alternate reference name\n    # and other status are given in key=value pairs after the null\n    # character.\n    PKT-LINE(<old-oid> <new-oid> <ref> ok\\0ref=refs/pull/123/head\n             forced-update)\n\nThe reporting function for \"receive-pack\" is also extended using a\nbackward compatible way by adding key-value pairs after an null\ncharacter, like:\n\n    # OK, run this command successfully with optional extended-status.\n    ok <reference>\\0ref=refs/pull/123/head old-oid=...\n\n    # NO, I reject it.\n    ng <reference> <error message>\n\nThis commit only includes changes on the server side, so we can make\na compatible test on old version of a Git client.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c       | 51 +++++++++++++++++----\n t/helper/test-proc-receive.c | 14 +++++-\n t/t5411/common-test-cases.sh | 88 ++++++++++++++++++++++++++++++++++--\n 3 files changed, 137 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex bdd0f0f448..9f5309a014 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -328,6 +328,7 @@ static void write_head_info(void)\n struct command {\n \tstruct command *next;\n \tconst char *error_string;\n+\tconst char *extended_status;\n \tunsigned int skip_update:1,\n \t\t     did_not_exist:1,\n \t\t     run_proc_receive:2;\n@@ -850,6 +851,7 @@ static int read_proc_receive_report(struct packet_reader *reader,\n {\n \tstruct command *cmd;\n \tstruct command *hint = NULL;\n+\tstruct strbuf extended_status = STRBUF_INIT;\n \tint code = 0;\n \n \tfor (;;) {\n@@ -858,9 +860,11 @@ static int read_proc_receive_report(struct packet_reader *reader,\n \t\tconst char *p;\n \t\tchar *status;\n \t\tchar *msg = NULL;\n+\t\tint len;\n \n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n \t\t\tbreak;\n+\t\tlen = strlen(reader->line);\n \t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n \t\t    *p++ != ' ' ||\n \t\t    parse_oid_hex(p, &new_oid, &p) ||\n@@ -912,6 +916,9 @@ static int read_proc_receive_report(struct packet_reader *reader,\n \t\t\telse\n \t\t\t\thint->error_string = \"failed\";\n \t\t\tcode = -1;\n+\t\t} else if (!strcmp(\"ft\", status)) {\n+\t\t\t/* Reset \"run_proc_receive\" field, and continue to run in \"receive-pack\" */\n+\t\t\thint->run_proc_receive = 0;\n \t\t} else if (strcmp(\"ok\", status)) {\n \t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n \t\t\t\t    status, reader->line);\n@@ -919,9 +926,24 @@ static int read_proc_receive_report(struct packet_reader *reader,\n \t\t\t/* Skip marking it as RUN_PROC_RECEIVE_RETURNED */\n \t\t\tcontinue;\n \t\t}\n-\t\toidcpy(&hint->old_oid, &old_oid);\n-\t\toidcpy(&hint->new_oid, &new_oid);\n-\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n+\t\tif (reader->pktlen > len)\n+\t\t\tstrbuf_addstr(&extended_status, (char *)reader->line + len + 1);\n+\t\tif (oidcmp(&hint->old_oid, &old_oid)) {\n+\t\t\toidcpy(&hint->old_oid, &old_oid);\n+\t\t\tstrbuf_addf(&extended_status, \"%sold-oid=%s\",\n+\t\t\t\t    extended_status.len > 0 ? \" \": \"\",\n+\t\t\t\t    oid_to_hex(&old_oid));\n+\t\t}\n+\t\tif (oidcmp(&hint->new_oid, &new_oid)) {\n+\t\t\toidcpy(&hint->new_oid, &new_oid);\n+\t\t\tstrbuf_addf(&extended_status, \"%snew-oid=%s\",\n+\t\t\t\t    extended_status.len > 0 ? \" \": \"\",\n+\t\t\t\t    oid_to_hex(&new_oid));\n+\t\t}\n+\t\tif (extended_status.len > 0)\n+\t\t\thint->extended_status = strbuf_detach(&extended_status, NULL);\n+\t\tif (hint->run_proc_receive)\n+\t\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n \t}\n \n \tfor (cmd = commands; cmd; cmd = cmd->next)\n@@ -2179,12 +2201,23 @@ static void report(struct command *commands, const char *unpack_status)\n \tpacket_buf_write(&buf, \"unpack %s\\n\",\n \t\t\t unpack_status ? unpack_status : \"ok\");\n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!cmd->error_string)\n-\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n-\t\t\t\t\t cmd->ref_name);\n-\t\telse\n-\t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n-\t\t\t\t\t cmd->ref_name, cmd->error_string);\n+\t\tif (!cmd->error_string) {\n+\t\t\tif (cmd->extended_status)\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s%c%s\\n\",\n+\t\t\t\t\t\t cmd->ref_name, '\\0',\n+\t\t\t\t\t\t cmd->extended_status);\n+\t\t\telse\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n+\t\t\t\t\t\t cmd->ref_name);\n+\t\t} else {\n+\t\t\tif (cmd->extended_status)\n+\t\t\t\tpacket_buf_write(&buf, \"ng %s %s%c%s\\n\",\n+\t\t\t\t\t\t cmd->ref_name, cmd->error_string,\n+\t\t\t\t\t\t '\\0', cmd->extended_status);\n+\t\t\telse\n+\t\t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n+\t\t\t\t\t\t cmd->ref_name, cmd->error_string);\n+\t\t}\n \t}\n \tpacket_buf_flush(&buf);\n \ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nindex 8fb143d692..c9f5c5aae4 100644\n--- a/t/helper/test-proc-receive.c\n+++ b/t/helper/test-proc-receive.c\n@@ -159,8 +159,18 @@ int cmd__proc_receive(int argc, const char **argv)\n \t}\n \n \tif (returns.nr)\n-\t\tfor_each_string_list_item(item, &returns)\n-\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n+\t\tfor_each_string_list_item(item, &returns) {\n+\t\t\tchar *p;\n+\n+\t\t\tp = strstr(item->string, \"\\\\0\");\n+\t\t\tif (p) {\n+\t\t\t\t*p = '\\0';\n+\t\t\t\tp += 2;\n+\t\t\t\tpacket_write_fmt(1, \"%s%c%s\\n\", item->string, '\\0', p);\n+\t\t\t} else {\n+\t\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n+\t\t\t}\n+\t\t}\n \tpacket_flush(1);\n \tsigchain_pop(SIGPIPE);\n \ndiff --git a/t/t5411/common-test-cases.sh b/t/t5411/common-test-cases.sh\nindex d6ad0e1bc8..35fe73c34e 100644\n--- a/t/t5411/common-test-cases.sh\n+++ b/t/t5411/common-test-cases.sh\n@@ -640,8 +640,8 @@ test_expect_success \"setup proc-receive hook\" '\n \n \ttest-tool proc-receive -v \\\n \t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n-\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n-\t\t-r \"$B $A refs/for/master/topic ok\"\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\\0ref=refs/pull/123/head\" \\\n+\t\t-r \"$B $A refs/for/master/topic ok\\0ref=refs/pull/124/head forced-update\"\n \tEOF\n \tchmod a+x \"$upstream/hooks/proc-receive\"\n '\n@@ -666,8 +666,8 @@ test_expect_success \"report update of all special refs\" '\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n-\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n-\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/master/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\\0ref=refs/pull/123/head\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/master/topic ok\\0ref=refs/pull/124/head forced-update\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n@@ -701,9 +701,10 @@ test_expect_success \"setup proc-receive hook\" '\n \n # Refs of upstream : master(A)\n # Refs of workbench: master(A)  tags/v123\n-# git push         :                       bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n test_expect_success \"report mixed refs update\" '\n \tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n \t\tHEAD:refs/heads/bar \\\n \t\tHEAD:refs/heads/baz \\\n \t\tHEAD:refs/for/next/topic \\\n@@ -713,6 +714,7 @@ test_expect_success \"report mixed refs update\" '\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n@@ -724,12 +726,14 @@ test_expect_success \"report mixed refs update\" '\n \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n \tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/for/master/topic ok\n \tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n \tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n \t * [new branch] HEAD -> bar\n \t * [new branch] HEAD -> baz\n \t * [new reference] HEAD -> refs/for/next/topic\n@@ -743,6 +747,80 @@ test_expect_success \"report mixed refs update\" '\n \t<COMMIT-A> refs/heads/bar\n \t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"config receive.procReceiveRefs for all refs\" '\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$B $A refs/heads/master ft\" \\\n+\t\t-r \"$A $ZERO_OID refs/heads/foo ft\" \\\n+\t\t-r \"$A $B refs/heads/bar ft\" \\\n+\t\t-r \"$A $B refs/for/master/topic ok\\0ref=refs/pull/123/head\" \\\n+\t\t-r \"$B $A refs/for/next/topic ok\\0ref=refs/pull/124/head forced-update\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"report test: fallthrough\" '\n+\tgit -C workbench push -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/heads/master ft\n+\tremote: proc-receive> <COMMIT-A> <ZERO-OID> refs/heads/foo ft\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/heads/bar ft\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/for/master/topic ok\\0ref=refs/pull/123/head\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/next/topic ok\\0ref=refs/pull/124/head forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/next/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> bar\n+\t - [deleted] foo\n+\t + <OID-B>...<OID-A> HEAD -> master (forced update)\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/master\n \tEOF\n \ttest_cmp expect actual\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395299","messageId":"20200412133022.17590-9-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200407120813.25025-1-worldhello.net@gmail.com","subject":"[PATCH v10 8/8] doc: add documentation for the proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-12T13:30:22Z","receivedAt":"2020-04-12T13:30:39Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd documentation for the new \"proc-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/githooks.txt | 58 ++++++++++++++++++++++++++++++++++++++\n 1 file changed, 58 insertions(+)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 3dccab5375..1c528e2fc4 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -333,6 +333,64 @@ The default 'update' hook, when enabled--and with\n `hooks.allowunannotated` config option unset or set to false--prevents\n unannotated tags to be pushed.\n \n+proc-receive\n+~~~~~~~~~~~~\n+\n+This hook is invoked by linkgit:git-receive-pack[1].  If the server has\n+set the multi-valued config variable `receive.procReceiveRefs`, and the\n+commands sent to 'receive-pack' have matching reference names, these\n+commands will be executed by this hook, instead of by the internal\n+`execute_commands()` function.  This hook is responsible for updating\n+the relevant references and reporting the results back to 'receive-pack'.\n+\n+This hook executes once for the receive operation.  It takes no\n+arguments, but uses a pkt-line format protocol to communicate with\n+'receive-pack' to read commands, push-options and send results.  In the\n+following example for the protocol, the letter 'S' stands for\n+'receive-pack' and the letter 'H' stands for this hook.\n+\n+    # Version and features negotiation.\n+    S: PKT-LINE(version=1\\0push-options atomic...)\n+    S: flush-pkt\n+    H: PKT-LINE(version=1\\0push-options...)\n+    H: flush-pkt\n+\n+    # Send commands from server to the hook.\n+    S: PKT-LINE(<old-oid> <new-oid> <ref>)\n+    S: ... ...\n+    S: flush-pkt\n+    # Send push-options only if the 'push-options' feature is enabled.\n+    S: PKT-LINE(push-option)\n+    S: ... ...\n+    S: flush-pkt\n+\n+    # Receive result from the hook.\n+    # OK, run this command successfully.\n+    H: PKT-LINE(<old-oid> <new-oid> <ref> ok)\n+    # NO, I reject it.\n+    H: PKT-LINE(<old-oid> <new-oid> <ref> ng <reason>)\n+    # Fall through, let 'receive-pack' to execute it.\n+    H: PKT-LINE(<old-oid> <new-oid> <ref> ft)\n+    # OK, but has an alternate reference.  The alternate reference name\n+    # and other status are given in key=value pairs after the null\n+    # character.\n+    H: PKT-LINE(<old-oid> <new-oid> <ref> ok\\0ref=refs/pull/123/head\n+                forced-update)\n+    H: ... ...\n+    H: flush-pkt\n+\n+Each command for the 'proc-receive' hook may point to a pseudo-reference\n+and always has a zero-old as its old-oid, while the 'proc-receive' hook\n+may update an alternate reference and the alternate reference may exist\n+already with a non-zero old-oid.  For this case, this hook may return\n+different OID and different reference name as extended status of the\n+report line.\n+\n+The report of the commands of this hook should have the same order as\n+the input.  The exit status of the 'proc-receive' hook only determines\n+the success or failure of the group of commands sent to it, unless\n+atomic push is in use.\n+\n [[post-receive]]\n post-receive\n ~~~~~~~~~~~~\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395300","messageId":"20200412133022.17590-8-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200407120813.25025-1-worldhello.net@gmail.com","subject":"[PATCH v10 7/8] send-pack: extension for client-side status report","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-12T13:30:21Z","receivedAt":"2020-04-12T13:30:39Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThis is the client-side extension for status report of git-push.\n\nThe \"proc-receive\" hook may receive a command for a pseudo-reference\nwith a zero-old as its old-oid, while the result of the hook may point\nto an alternate reference and the reference may exist already with a\nnon-zero old-oid.  Git client may receive a report with extened status\nas the following example:\n\n    ok refs/for/master/topic\\0ref=refs/pull/123/head old-oid=...\n\nParse the key-value pairs in the extended status after the null\ncharacter of the report, and show the proper reference name, old-oid\nand new-oid to the user.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n remote.c                     | 28 +++++++++++++++\n remote.h                     |  3 ++\n send-pack.c                  | 13 ++++++-\n t/t5411/common-test-cases.sh | 10 +++---\n transport-helper.c           | 66 +++++++++++++++++++-----------------\n transport.c                  | 33 +++++++++++++++---\n 6 files changed, 112 insertions(+), 41 deletions(-)\n\ndiff --git a/remote.c b/remote.c\nindex c43196ec06..0b22d9e14c 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"config.h\"\n+#include \"connect.h\"\n #include \"remote.h\"\n #include \"refs.h\"\n #include \"refspec.h\"\n@@ -2311,3 +2312,30 @@ void apply_push_cas(struct push_cas_option *cas,\n \tfor (ref = remote_refs; ref; ref = ref->next)\n \t\tapply_cas(cas, remote, ref);\n }\n+\n+void update_ref_from_remote_status(struct ref *ref)\n+{\n+\tchar *val;\n+\tchar c;\n+\tint len;\n+\n+\tif (!ref->remote_status)\n+\t\treturn;\n+\n+\tval = (char *)parse_feature_value(ref->remote_status, \"old-oid\", &len);\n+\tif (val && len) {\n+\t\tc = *(val+len);\n+\t\t*(val+len) = '\\0';\n+\t\tget_oid_hex(val, &ref->old_oid);\n+\t\t*(val+len) = c;\n+\t}\n+\tval = (char *)parse_feature_value(ref->remote_status, \"new-oid\", &len);\n+\tif (val && len) {\n+\t\tc = *(val+len);\n+\t\t*(val+len) = '\\0';\n+\t\tget_oid_hex(val, &ref->new_oid);\n+\t\t*(val+len) = c;\n+\t}\n+\tif (parse_feature_request(ref->remote_status, \"forced-update\"))\n+\t\tref->forced_update = 1;\n+}\ndiff --git a/remote.h b/remote.h\nindex 11d8719b58..09cf7014e3 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -345,4 +345,7 @@ int parseopt_push_cas_option(const struct option *, const char *arg, int unset);\n int is_empty_cas(const struct push_cas_option *);\n void apply_push_cas(struct push_cas_option *, struct remote *, struct ref *);\n \n+/* Parse key-value pairs of remote_status and update the reference accordingly */\n+void update_ref_from_remote_status(struct ref *ref);\n+\n #endif\ndiff --git a/send-pack.c b/send-pack.c\nindex 0407841ae8..ab294c9426 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -159,6 +159,9 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \twhile (1) {\n \t\tconst char *refname;\n \t\tchar *msg;\n+\t\tchar *extended_status = NULL;\n+\t\tint len;\n+\n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n \t\t\tbreak;\n \t\tif (!starts_with(reader->line, \"ok \") && !starts_with(reader->line, \"ng \")) {\n@@ -167,10 +170,13 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \t\t\tbreak;\n \t\t}\n \n+\t\tlen = strlen(reader->line);\n \t\trefname = reader->line + 3;\n \t\tmsg = strchr(refname, ' ');\n \t\tif (msg)\n \t\t\t*msg++ = '\\0';\n+\t\tif (reader->pktlen > len)\n+\t\t\textended_status = (char *)reader->line + len + 1;\n \n \t\t/* first try searching at our hint, falling back to all refs */\n \t\tif (hint)\n@@ -194,7 +200,12 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \t\t\thint->status = REF_STATUS_REMOTE_REJECT;\n \t\t\tret = -1;\n \t\t}\n-\t\thint->remote_status = xstrdup_or_null(msg);\n+\t\tif (msg) {\n+\t\t\thint->remote_status = xstrdup(msg);\n+\t\t} else if (extended_status) {\n+\t\t\thint->remote_status = xstrdup(extended_status);\n+\t\t\tupdate_ref_from_remote_status(hint);\n+\t\t}\n \t\t/* start our next search from the next ref */\n \t\thint = hint->next;\n \t}\ndiff --git a/t/t5411/common-test-cases.sh b/t/t5411/common-test-cases.sh\nindex 35fe73c34e..413e6502be 100644\n--- a/t/t5411/common-test-cases.sh\n+++ b/t/t5411/common-test-cases.sh\n@@ -673,9 +673,9 @@ test_expect_success \"report update of all special refs\" '\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \t * [new reference] HEAD -> refs/review/a/b/c/topic\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -738,7 +738,7 @@ test_expect_success \"report mixed refs update\" '\n \t * [new branch] HEAD -> baz\n \t * [new reference] HEAD -> refs/for/next/topic\n \t * [new branch] HEAD -> foo\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -812,8 +812,8 @@ test_expect_success \"report test: fallthrough\" '\n \t <OID-A>..<OID-B> <COMMIT-B> -> bar\n \t - [deleted] foo\n \t + <OID-B>...<OID-A> HEAD -> master (forced update)\n-\t * [new reference] HEAD -> refs/for/master/topic\n-\t * [new reference] HEAD -> refs/for/next/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/pull/123/head\n+\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 20a7185ec4..d2179631cf 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -747,37 +747,39 @@ static int push_update_ref_status(struct strbuf *buf,\n \t\t\tmsg = xstrdup(msg);\n \t\tstrbuf_release(&msg_buf);\n \n-\t\tif (!strcmp(msg, \"no match\")) {\n-\t\t\tstatus = REF_STATUS_NONE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"up to date\")) {\n-\t\t\tstatus = REF_STATUS_UPTODATE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"non-fast forward\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_NONFASTFORWARD;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"already exists\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_ALREADY_EXISTS;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"fetch first\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_FETCH_FIRST;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"needs force\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_NEEDS_FORCE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"stale info\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_STALE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"forced update\")) {\n-\t\t\tforced = 1;\n-\t\t\tFREE_AND_NULL(msg);\n+\t\tif (status != REF_STATUS_OK) {\n+\t\t\tif (!strcmp(msg, \"no match\")) {\n+\t\t\t\tstatus = REF_STATUS_NONE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"up to date\")) {\n+\t\t\t\tstatus = REF_STATUS_UPTODATE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"non-fast forward\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_NONFASTFORWARD;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"already exists\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_ALREADY_EXISTS;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"fetch first\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_FETCH_FIRST;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"needs force\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_NEEDS_FORCE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"stale info\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_STALE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"forced update\")) {\n+\t\t\t\tforced = 1;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n \t\t}\n \t}\n \n@@ -802,6 +804,8 @@ static int push_update_ref_status(struct strbuf *buf,\n \t(*ref)->status = status;\n \t(*ref)->forced_update |= forced;\n \t(*ref)->remote_status = msg;\n+\tif (status == REF_STATUS_OK && (*ref)->remote_status)\n+\t\tupdate_ref_from_remote_status(*ref);\n \treturn !(status == REF_STATUS_OK);\n }\n \ndiff --git a/transport.c b/transport.c\nindex 272c0f4046..b0451c7a3c 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -463,11 +463,30 @@ static void print_ref_status(char flag, const char *summary,\n \t\t\t     struct ref *to, struct ref *from, const char *msg,\n \t\t\t     int porcelain, int summary_width)\n {\n+\tchar *from_name = NULL;\n+\tchar *to_name = NULL;\n+\tconst char *val;\n+\tint len;\n+\n+\tif (from && from->remote_status) {\n+\t\tval = parse_feature_value(from->remote_status, \"ref\", &len);\n+\t\tif (val && len)\n+\t\t\t from_name = xmemdupz(val, len);\n+\t}\n+\tif (to && to->remote_status) {\n+\t\tval = parse_feature_value(to->remote_status, \"ref\", &len);\n+\t\tif (val && len)\n+\t\t\t to_name = xmemdupz(val, len);\n+\t}\n+\n \tif (porcelain) {\n \t\tif (from)\n-\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag,\n+\t\t\t\tfrom_name ? from_name : from->name,\n+\t\t\t\tto_name ? to_name: to->name);\n \t\telse\n-\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag,\n+\t\t\t\tto_name ? to_name: to->name);\n \t\tif (msg)\n \t\t\tfprintf(stdout, \"%s (%s)\\n\", summary, msg);\n \t\telse\n@@ -481,9 +500,11 @@ static void print_ref_status(char flag, const char *summary,\n \t\tfprintf(stderr, \" %s%c %-*s%s \", red, flag, summary_width,\n \t\t\tsummary, reset);\n \t\tif (from)\n-\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(to->name));\n+\t\t\tfprintf(stderr, \"%s -> %s\",\n+\t\t\t\tprettify_refname(from_name ? from_name : from->name),\n+\t\t\t\tprettify_refname(to_name ? to_name : to->name));\n \t\telse\n-\t\t\tfputs(prettify_refname(to->name), stderr);\n+\t\t\tfputs(prettify_refname(to_name ? to_name : to->name), stderr);\n \t\tif (msg) {\n \t\t\tfputs(\" (\", stderr);\n \t\t\tfputs(msg, stderr);\n@@ -491,6 +512,10 @@ static void print_ref_status(char flag, const char *summary,\n \t\t}\n \t\tfputc('\\n', stderr);\n \t}\n+\tif (from_name)\n+\t\tfree(from_name);\n+\tif (to_name)\n+\t\tfree(to_name);\n }\n \n static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_width)\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395302","messageId":"20200412133022.17590-5-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200407120813.25025-1-worldhello.net@gmail.com","subject":"[PATCH v10 4/8] receive-pack: new config receive.procReceiveRefs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-12T13:30:18Z","receivedAt":"2020-04-12T13:30:39Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd a new multi-valued config variable \"receive.procReceiveRefs\"\nfor `receive-pack` command, like the follows:\n\n    git config --system --add receive.procReceiveRefs refs/for\n    git config --system --add receive.procReceiveRefs refs/drafts\n\nIf the specific prefix strings match the reference names of the commands\nwhich are sent from git client to `receive-pack`, these commands will be\nexecuted by an external hook (named \"proc-receive\"), instead of the\ninternal `execute_commands` function.\n\nFor example, if it is set to \"refs/for\", pushing to a reference such as\n\"refs/for/master\" will not create or update reference \"refs/for/master\",\nbut may create or update a pull request directly by running the hook\n\"proc-receive\".\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/config/receive.txt |  14 ++++\n builtin/receive-pack.c           |  43 ++++++++--\n t/t5411/common-test-cases.sh     | 130 +++++++++++++++++++++++++++++++\n 3 files changed, 180 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/receive.txt b/Documentation/config/receive.txt\nindex 65f78aac37..e7b967feeb 100644\n--- a/Documentation/config/receive.txt\n+++ b/Documentation/config/receive.txt\n@@ -114,6 +114,20 @@ receive.hideRefs::\n \tAn attempt to update or delete a hidden ref by `git push` is\n \trejected.\n \n+receive.procReceiveRefs::\n+\tThis is a multi-valued variable that defines reference prefixes\n+\tto match the commands in `receive-pack`.  Commands matching the\n+\tprefixes will be executed by an external hook \"proc-receive\",\n+\tinstead of the internal `execute_commands` function.  If this\n+\tvariable is not defined, the \"proc-receive\" hook will never be\n+\tused, and all commands will be executed by the internal\n+\t`execute_commands` function.\n+\n+\tFor example, if this variable is set to \"refs/for\", pushing to\n+\treference such as \"refs/for/master\" will not create or update a\n+\treference named \"refs/for/master\", but may create or update a\n+\tpull request directly by running the hook \"proc-receive\".\n+\n receive.updateServerInfo::\n \tIf set to true, git-receive-pack will run git-update-server-info\n \tafter receiving data from git-push and updating refs.\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 1a57c5bb3b..bdd0f0f448 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -76,6 +76,7 @@ static struct object_id push_cert_oid;\n static struct signature_check sigcheck;\n static const char *push_cert_nonce;\n static const char *cert_nonce_seed;\n+static struct string_list proc_receive_refs;\n \n static const char *NONCE_UNSOLICITED = \"UNSOLICITED\";\n static const char *NONCE_BAD = \"BAD\";\n@@ -228,6 +229,20 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (strcmp(var, \"receive.procreceiverefs\") == 0) {\n+\t\tchar *prefix;\n+\t\tint len;\n+\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tprefix = xstrdup(value);\n+\t\tlen = strlen(prefix);\n+\t\twhile (len && prefix[len - 1] == '/')\n+\t\t\tprefix[--len] = '\\0';\n+\t\tstring_list_insert(&proc_receive_refs, prefix);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -1734,15 +1749,26 @@ static void execute_commands(struct command *commands,\n \t/* Try to find commands that have special prefix in their reference names,\n \t * and mark them to run an external \"proc-receive\" hook later.\n \t */\n-\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n-\t\t\tcontinue;\n+\tif (proc_receive_refs.nr > 0) {\n+\t\tstruct strbuf refname_full = STRBUF_INIT;\n+\t\tsize_t prefix_len;\n+\n+\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n+\t\tprefix_len = refname_full.len;\n \n-\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n-\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n-\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n-\t\t\trun_proc_receive = 1;\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tif (!should_process_cmd(cmd))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_setlen(&refname_full, prefix_len);\n+\t\t\tstrbuf_addstr(&refname_full, cmd->ref_name);\n+\t\t\tif (ref_is_matched(&proc_receive_refs, cmd->ref_name, refname_full.buf)) {\n+\t\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\t\trun_proc_receive = 1;\n+\t\t\t}\n \t\t}\n+\n+\t\tstrbuf_release(&refname_full);\n \t}\n \n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n@@ -2196,6 +2222,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n+\tstring_list_init(&proc_receive_refs, 0);\n+\n \tpacket_trace_identity(\"receive-pack\");\n \n \targc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);\n@@ -2311,5 +2339,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \toid_array_clear(&shallow);\n \toid_array_clear(&ref);\n \tfree((void *)push_cert_nonce);\n+\tstring_list_clear(&proc_receive_refs, 0);\n \treturn 0;\n }\ndiff --git a/t/t5411/common-test-cases.sh b/t/t5411/common-test-cases.sh\nindex 8e30c8f31e..d6ad0e1bc8 100644\n--- a/t/t5411/common-test-cases.sh\n+++ b/t/t5411/common-test-cases.sh\n@@ -53,6 +53,14 @@ test_expect_success \"cleanup\" '\n \t)\n '\n \n+test_expect_success \"add two receive.procReceiveRefs settings\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit config --add receive.procReceiveRefs refs/for &&\n+\t\tgit config --add receive.procReceiveRefs refs/review/\n+\t)\n+'\n+\n # Refs of upstream : master(A)\n # Refs of workbench: master(A)  tags/v123\n # git push         :                       next(A)  refs/for/master/topic(A)\n@@ -617,3 +625,125 @@ test_expect_success \"push with options\" '\n \tEOF\n \ttest_cmp expect actual\n '\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n+\t\t-r \"$B $A refs/for/master/topic ok\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/review/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"report update of all special refs\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/review/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/review/a/b/c/topic\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n+\t#!/bin/sh\n+\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n+\t\t-r \"$A $B refs/for/master/topic ok\"\n+\tEOF\n+\tchmod a+x \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"report mixed refs update\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> bar\n+\t * [new branch] HEAD -> baz\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new branch] HEAD -> foo\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395301","messageId":"20200412133022.17590-6-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200407120813.25025-1-worldhello.net@gmail.com","subject":"[PATCH v10 5/8] connect: export parse_feature_value()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-12T13:30:19Z","receivedAt":"2020-04-12T13:30:40Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nExport `parse_feature_value()` to parse extended status of report for\n\"proc-receive\" and \"receive-pack\".\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n connect.c | 3 +--\n connect.h | 1 +\n 2 files changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex b6451ab5e8..08e0616726 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -18,7 +18,6 @@\n \n static char *server_capabilities_v1;\n static struct argv_array server_capabilities_v2 = ARGV_ARRAY_INIT;\n-static const char *parse_feature_value(const char *, const char *, int *);\n \n static int check_ref(const char *name, unsigned int flags)\n {\n@@ -447,7 +446,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \treturn list;\n }\n \n-static const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp)\n+const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp)\n {\n \tint len;\n \ndiff --git a/connect.h b/connect.h\nindex 5f2382e018..17b3252cbc 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -12,6 +12,7 @@ int finish_connect(struct child_process *conn);\n int git_connection_is_socket(struct child_process *conn);\n int server_supports(const char *feature);\n int parse_feature_request(const char *features, const char *feature);\n+const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret);\n const char *server_feature_value(const char *feature, int *len_ret);\n int url_is_local_not_ssh(const char *url);\n \n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395304","messageId":"xmqqa73ge9hj.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200412133022.17590-2-worldhello.net@gmail.com","subject":"Re: [PATCH v10 1/8] transport: not report a non-head push as a branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-04-12T20:26:32Z","receivedAt":"2020-04-12T20:26:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n>  create mode 100755 t/t5411-proc-receive-hook.sh\n>  create mode 100644 t/t5411/common-functions.sh\n>  create mode 100644 t/t5411/common-test-cases.sh\n>  create mode 100755 t/t5412-proc-receive-hook-http-protocol.sh\n>\n> diff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\n> new file mode 100755\n> index 0000000000..ef289fe92a\n> --- /dev/null\n> +++ b/t/t5411-proc-receive-hook.sh\n> @@ -0,0 +1,75 @@\n> +#!/bin/sh\n> +#\n> +# Copyright (c) 2020 Jiang Xin\n> +#\n> +\n> +test_description='Test proc-receive hook'\n> +\n> +. ./test-lib.sh\n> +\n> +. \"$TEST_DIRECTORY\"/t5411/common-functions.sh\n> +\n> +# Format the output of git-push, git-show-ref and other commands to make a\n> +# user-friendly and stable text.  In addition to the common format method,\n> +# we also replace the URL of different protocol for the upstream repository\n> +# with a fixed pattern.\n> +make_user_friendly_and_stable_output () {\n> +\tmake_user_friendly_and_stable_output_common | sed \\\n> +\t\t-e \"s#To ../upstream.git#To <URL/of/upstream.git>#\"\n\nBreak the line immediately after the pipe, and begin the next line\nwith the command.  That way, the shell knows that you haven't\nfinished giving the command at the end of the first line, and you do\nnot have to use a backslash, i.e.\n\n    make_user_friendly |\n    sed -e 's/from/to/'\n\n> +\t\tcd workbench &&\n> +\t\t# Try to make a stable fixed width for abbreviated commit ID,\n> +\t\t# this fixed-width oid will be replaced with \"<OID>\".\n> +\t\tgit config core.abbrev 7 &&\n> +\t\tgit remote add origin ../upstream.git &&\n> +\t\tgit update-ref refs/heads/master $A &&\n> +\t\tgit tag -m \"v123\" v123 $A &&\n> +\t\tgit push origin \\\n> +\t\t\t$B:refs/heads/master \\\n> +\t\t\t$A:refs/heads/next\n> +\t) &&\n> +\tTAG=$(git -C workbench rev-parse v123) &&\n> +\n> +\t# setup pre-receive hook\n> +\tcat >upstream.git/hooks/pre-receive <<-\\EOF &&\n\nUse write_script and you don't need the chmod at the end.\n\n> +\t#!/bin/sh\n> +\n> +\techo >&2 \"# pre-receive hook\"\n> +\n> +\twhile read old new ref\n> +\tdo\n> +\t\techo >&2 \"pre-receive< $old $new $ref\"\n> +\tdone\n> +\tEOF\n\nPerhaps \"exec >&2\" upfront?\n\n> +\t# setup post-receive hook\n> +\tcat >upstream.git/hooks/post-receive <<-\\EOF &&\n> +\t#!/bin/sh\n> +\n> +\techo >&2 \"# post-receive hook\"\n> +\n> +\twhile read old new ref\n> +\tdo\n> +\t\techo >&2 \"post-receive< $old $new $ref\"\n> +\tdone\n> +\tEOF\n> +\n> +\tchmod a+x \\\n> +\t\tupstream.git/hooks/pre-receive \\\n> +\t\tupstream.git/hooks/post-receive &&\n\nDitto.\n\n> +\tupstream=upstream.git\n> +'\n> +\n> +# Include test cases for both file and HTTP protocol\n> +. \"$TEST_DIRECTORY\"/t5411/common-test-cases.sh\n> +\n> +test_done\n\n\n> diff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\n> new file mode 100644\n> index 0000000000..6e400c0625\n> --- /dev/null\n> +++ b/t/t5411/common-functions.sh\n> @@ -0,0 +1,50 @@\n\nIt is good that this omits \"#!\" and leaves the file without +x bit.\n\n> +# Format the output of git-push, git-show-ref and other commands to make a\n> +# user-friendly and stable text.  We can easily prepare the expect text\n> +# without having to worry about future changes of the commit ID and spaces\n> +# of the output.  We also replce single quotes with double quotes, because\n> +# it is boring to prepare unquoted single quotes in expect txt.\n\ns/txt/text/\n\n> +make_user_friendly_and_stable_output_common () {\n> +\tsed \\\n> +\t\t-e \"s/  *\\$//\" \\\n> +\t\t-e \"s/   */ /g\" \\\n> +\t\t-e \"s/'/\\\"/g\" \\\n> +\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n> +\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n> +\t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n> +\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n> +\t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n> +\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\"\n> +}\n\nSo a full object name for commit \"A\" becomes <COMMIT-A> while an\nabbreviated one becomes <OID-A>?  OK.  I wonder if we force the full\nlength (i.e. no abbreviation), the tests become simpler, but it is\nOK.  As long as A and B does not share the same 7 hexdigits in any\nfuture hash function we choose, the above will do the right thing\n;-)\n\n> diff --git a/t/t5412-proc-receive-hook-http-protocol.sh b/t/t5412-proc-receive-hook-http-protocol.sh\n> new file mode 100755\n> index 0000000000..e2446d4d32\n> --- /dev/null\n> +++ b/t/t5412-proc-receive-hook-http-protocol.sh\n> @@ -0,0 +1,86 @@\n> +#!/bin/sh\n> +#\n> +# Copyright (c) 2020 Jiang Xin\n> +#\n> +\n> +test_description='Test proc-receive hook for HTTP protocol'\n> +\n> +. ./test-lib.sh\n> +\n> +ROOT_PATH=\"$PWD\"\n> +. \"$TEST_DIRECTORY\"/lib-gpg.sh\n> +. \"$TEST_DIRECTORY\"/lib-httpd.sh\n> +. \"$TEST_DIRECTORY\"/lib-terminal.sh\n> +start_httpd\n> +\n> +. \"$TEST_DIRECTORY\"/t5411/common-functions.sh\n> +\n> +# Format the output of git-push, git-show-ref and other commands to make a\n> +# user-friendly and stable text.  In addition to the common format method,\n> +# we also replace the URL of different protocol for the upstream repository\n> +# with a fixed pattern.\n> +make_user_friendly_and_stable_output () {\n> +\tmake_user_friendly_and_stable_output_common | sed \\\n> +\t\t-e \"s#To http:.*/upstream.git#To <URL/of/upstream.git>#\"\n> +}\n\nDitto.\n\n> +# Refs of upstream : master(B)  next(A)\n> +# Refs of workbench: master(A)           tags/v123\n> +test_expect_success \"setup\" '\n> +\tgit init --bare upstream.git &&\n> +\tgit -C upstream.git config http.receivepack true &&\n> +\tgit init workbench &&\n> +\tcreate_commits_in workbench A B &&\n> +\t(\n> +\t\tcd workbench &&\n> +\t\t# Try to make a stable fixed width for abbreviated commit ID,\n> +\t\t# this fixed-width oid will be replaced with \"<OID>\".\n> +\t\tgit config core.abbrev 7 &&\n> +\t\tgit remote add origin ../upstream.git &&\n> +\t\tgit update-ref refs/heads/master $A &&\n> +\t\tgit tag -m \"v123\" v123 $A &&\n> +\t\tgit push origin \\\n> +\t\t\t$B:refs/heads/master \\\n> +\t\t\t$A:refs/heads/next\n> +\t) &&\n> +\tTAG=$(git -C workbench rev-parse v123) &&\n> +\n> +\t# setup pre-receive hook\n> +\tcat >upstream.git/hooks/pre-receive <<-\\EOF &&\n> +\t#!/bin/sh\n\nDitto.\n\n> +\n> +\techo >&2 \"# pre-receive hook\"\n> +\n> +\twhile read old new ref\n> +\tdo\n> +\t\techo >&2 \"pre-receive< $old $new $ref\"\n> +\tdone\n> +\tEOF\n> +\n> +\t# setup post-receive hook\n> +\tcat >upstream.git/hooks/post-receive <<-\\EOF &&\n> +\t#!/bin/sh\n> +\n> +\techo >&2 \"# post-receive hook\"\n> +\n> +\twhile read old new ref\n> +\tdo\n> +\t\techo >&2 \"post-receive< $old $new $ref\"\n> +\tdone\n> +\tEOF\n> +\n> +\tchmod a+x \\\n> +\t\tupstream.git/hooks/pre-receive \\\n> +\t\tupstream.git/hooks/post-receive &&\n> +\n> +\tupstream=\"$HTTPD_DOCUMENT_ROOT_PATH/upstream.git\" &&\n> +\tmv upstream.git \"$upstream\" &&\n> +\tgit -C workbench remote set-url origin $HTTPD_URL/smart/upstream.git\n> +'\n> +\n> +setup_askpass_helper\n> +\n> +# Include test cases for both file and HTTP protocol\n> +. \"$TEST_DIRECTORY\"/t5411/common-test-cases.sh\n> +\n> +test_done\n> diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\n> index 9ff041a093..9e4b9313b5 100755\n> --- a/t/t5516-fetch-push.sh\n> +++ b/t/t5516-fetch-push.sh\n> @@ -1039,7 +1039,7 @@ test_force_fetch_tag \"annotated tag\" \"-f -a -m'tag message'\"\n>  test_expect_success 'push --porcelain' '\n>  \tmk_empty testrepo &&\n>  \techo >.git/foo  \"To testrepo\" &&\n> -\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new branch]\"  &&\n> +\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new reference]\"  &&\n>  \techo >>.git/foo \"Done\" &&\n>  \tgit push >.git/bar --porcelain  testrepo refs/heads/master:refs/remotes/origin/master &&\n>  \t(\n> diff --git a/transport.c b/transport.c\n> index 1fdc7dac1a..272c0f4046 100644\n> --- a/transport.c\n> +++ b/transport.c\n> @@ -500,9 +500,12 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n>  \t\t\t\t porcelain, summary_width);\n>  \telse if (is_null_oid(&ref->old_oid))\n\nThis block is about an update to missing \"old\", i.e. a creation.\n\n>  \t\tprint_ref_status('*',\n> -\t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n> -\t\t\t\"[new branch]\"),\n> -\t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n\nWe used to say \"if outside refs/tags/, let's call it branch\".\n\n> +\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n> +\t\t\t\t  ? \"[new tag]\"\n> +\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n> +\t\t\t\t     ? \"[new branch]\"\n> +\t\t\t\t     : \"[new reference]\")),\n> +\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n\nNow we say \"outside refs/heads/ and refs/tags/, that's just a\nreference, different from branch\".  OK.\n\nDo we ever update refs/remotes/ with this codepath?  Would we want\nto label it differently, i.e. as \"remote-tracking branch\"?\n\n>  \telse {\n>  \t\tstruct strbuf quickref = STRBUF_INIT;\n>  \t\tchar type;\n\nThanks.\n"},{"id":"395307","messageId":"xmqq5ze4e6j2.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200412133022.17590-3-worldhello.net@gmail.com","subject":"Re: [PATCH v10 2/8] receive-pack: add new proc-receive hook","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-04-12T21:30:25Z","receivedAt":"2020-04-12T21:30:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> The filter will check the prefix of the reference name, and if the\n> command has a special reference name, the filter will turn a specific\n> field (`run_proc_receive`) on for the command.  Commands with this filed\n> turned on will be executed by a new handler (an hook named\n> \"proc-receive\") instead of the internal `execute_commands` function.\n> We can use this \"proc-receive\" command to create pull requests or send\n> emails for code review.\n>\n> Suggested by Junio, this \"proc-receive\" hook reads the commands,\n> push-options (optional), and send result using a protocol in pkt-line\n> format.  In the following example, The letter \"S\" stands for\n> \"receive-pack\" and letter \"H\" stands for the hook.\n\nHeh, you're giving me too much credit here.  The exchange\nillustrated below makes sense to me, ...\n\n>     # Version and features negotiation.\n>     S: PKT-LINE(version=1\\0push-options atomic...)\n>     S: flush-pkt\n>     H: PKT-LINE(version=1\\0push-options...)\n>     H: flush-pkt\n>\n>     # Send commands from server to the hook.\n>     S: PKT-LINE(<old-oid> <new-oid> <ref>)\n>     S: ... ...\n>     S: flush-pkt\n>     # Send push-options only if the 'push-options' feature is enabled.\n>     S: PKT-LINE(push-option)\n>     S: ... ...\n>     S: flush-pkt\n>\n>     # Receive result from the hook.\n>     # OK, run this command successfully.\n>     H: PKT-LINE(<old-oid> <new-oid> <ref> ok)\n>     # NO, I reject it.\n>     H: PKT-LINE(<old-oid> <new-oid> <ref> ng <reason>)\n>     # Fall through, let 'receive-pack' to execute it.\n>     H: PKT-LINE(<old-oid> <new-oid> <ref> ft)\n>     # OK, but has an alternate reference.  The alternate reference name\n>     # and other status are given in key=value pairs after the null\n>     # character.\n>     H: PKT-LINE(<old-oid> <new-oid> <ref> ok\\0ref=refs/pull/123/head\n>                 forced-update)\n\nThe semantics of this one is fuzzy.  We made an update to a ref that\nis different from the one that was requested (presumably that is what\nis reported after \"\\0ref=\"), OK, but did we update the ref in the\nrequest, too, or did we leave the ref in the request intact?  Or, do\nwe say \"no\\0ref=...\" if we don't update the requested ref and\ninstead update a different one?  Let's hold the answer at this point\nand keep thinking...\n\n>     H: ... ...\n>     H: flush-pkt\n>\n> After receiving a command, the hook will execute the command, and may\n> create/update different reference.  For example, a command for a pseudo\n> reference \"refs/for/master/topic\" may create/update different reference\n> such as \"refs/pull/123/head\".  The alternate reference name and other\n> status are given in key-value pairs as extended status of the report\n> line.\n>\n> The list of commands returned from \"proc-receive\" will replace the\n> relevant commands that are sent from user to \"receive-pack\", and\n> \"receive-pack\" will continue to run the \"execute_commands\" function and\n> other routines.  Finally, the result of the execution of these commands\n> will be reported to end user.\n\nIt is not clear if there needs to be any correspondence between the\nsequence of input commands and the sequence of output commands.  I\nam guessing that there is not any and the hook is allowed to do\nanything as it wants to.  For example:\n\n - it is OK to execute them all and report them, but in a totally\n   random order.\n\n - it is OK to ignore all input and report no update (not even\n   \"ng\").\n\n - it is OK to be asked to update one ref, but update and report\n   updates to two refs that are not related to the requested ref.\n\nIs my understanding correct?\n\nOr does the design tie the set of input and output ref updates\nclosely together, i.e. one input corresponds to one output and they\nare in order, so all the hook is allowed to do is\n\n - to execute and update as it is told, and report \"ok\",\n\n - to reject and report \"ng\", or\n\n - to update *another* ref without updating the requested one, with\n   \"ok\\0ref=...\" mechanism.\n\nI am not sure which one is more sensible, though.\n\nIf we choose to use the \"anything goes\" approach, I do not think\nthere is no need for the \"ok\\nref=...\" mechanism---we can just give\ntwo output records, one \"ok\" for the original request, and a new\n\"ok\" that reports the \"we updated this one instead\".\n\n> +#define RUN_PROC_RECEIVE_SCHEDULED\t1\n> +#define RUN_PROC_RECEIVE_RETURNED\t2\n>  struct command {\n>  \tstruct command *next;\n>  \tconst char *error_string;\n>  \tunsigned int skip_update:1,\n> -\t\t     did_not_exist:1;\n> +\t\t     did_not_exist:1,\n> +\t\t     run_proc_receive:2;\n>  \tint index;\n>  \tstruct object_id old_oid;\n>  \tstruct object_id new_oid;\n> @@ -817,6 +820,236 @@ static int run_update_hook(struct command *cmd)\n>  \treturn finish_command(&proc);\n>  }\n>  \n> +static struct command *find_command_by_refname(const struct command *list,\n> +\t\t\t\t\t       const char *refname)\n> +{\n> +\tfor (; list; list = list->next)\n> +\t\tif (!strcmp(list->ref_name, refname))\n> +\t\t\treturn (struct command *)list;\n> +\treturn NULL;\n> +}\n> +\n> +static int read_proc_receive_report(struct packet_reader *reader,\n> +\t\t\t\t    struct command *commands,\n> +\t\t\t\t    struct strbuf *errmsg)\n> +{\n> +\tstruct command *cmd;\n> +\tstruct command *hint = NULL;\n> +\tint code = 0;\n> +\n> +\tfor (;;) {\n> +\t\tstruct object_id old_oid, new_oid;\n> +\t\tconst char *refname;\n> +\t\tconst char *p;\n> +\t\tchar *status;\n> +\t\tchar *msg = NULL;\n> +\n> +\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n> +\t\t\tbreak;\n> +\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n> +\t\t    *p++ != ' ' ||\n> +\t\t    parse_oid_hex(p, &new_oid, &p) ||\n> +\t\t    *p++ != ' ') {\n> +\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\\n\",\n> +\t\t\t\t    reader->line);\n> +\t\t\tcode = -1;\n> +\t\t\tcontinue;\n> +\t\t}\n> +\n> +\t\trefname = p;\n> +\t\tstatus = strchr(p, ' ');\n> +\t\tif (!status) {\n> +\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\\n\",\n> +\t\t\t\t    reader->line);\n> +\t\t\tcode = -1;\n> +\t\t\tcontinue;\n> +\t\t}\n> +\t\t*status++ = '\\0';\n> +\t\tif (strlen(status) > 2 && *(status + 2) == ' ') {\n> +\t\t\tmsg = status + 2;\n> +\t\t\t*msg++ = '\\0';\n> +\t\t}\n\nSo, here we parsed \"<old> <new> <ref> (ok|ng)\", with status pointing\nat ok/ng, or \"<old> <new> <ref> (ok|ng) <why>\", with status pointing\nat ok/ng and msg pointing at <why>.  refname points at <ref>.\n\nMost notably, \"ok\\0ref=...\" is not handled any specially from a\nnormal \"ok\" here.\n\n> +\t\tif (strlen(status) != 2) {\n> +\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n> +\t\t\t\t    status, reader->line);\n> +\t\t\tcode = -1;\n> +\t\t\tcontinue;\n> +\t\t}\n> +\n> +\t\t/* first try searching at our hint, falling back to all refs */\n> +\t\tif (hint)\n> +\t\t\thint = find_command_by_refname(hint, refname);\n> +\t\tif (!hint)\n> +\t\t\thint = find_command_by_refname(commands, refname);\n> +\t\tif (!hint) {\n> +\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unknown ref: %s\\n\",\n> +\t\t\t\t    refname);\n> +\t\t\tcontinue;\n> +\t\t}\n> +\t\tif (!hint->run_proc_receive) {\n> +\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on ref of builtin command: %s\\n\",\n> +\t\t\t\t    refname);\n> +\t\t\tcontinue;\n\nUpdates against a request to update certain ref hierarchies by the\nhook is rejected here.\n\n> +\t\t}\n> +\t\tif (!strcmp(status, \"ng\")) {\n> +\t\t\tif (msg)\n> +\t\t\t\thint->error_string = xstrdup(msg);\n> +\t\t\telse\n> +\t\t\t\thint->error_string = \"failed\";\n> +\t\t\tcode = -1;\n\nThe <why> in \"(ok|ng) <why>\" message is only used for \"ng\", which is\nprobably good, as the reason why something succeeded is not usually\nvery interesting.\n\n> +\t\t} else if (strcmp(\"ok\", status)) {\n> +\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n> +\t\t\t\t    status, reader->line);\n> +\t\t\tcode = -1;\n> +\t\t\t/* Skip marking it as RUN_PROC_RECEIVE_RETURNED */\n\nBut then shouldn't we be complaining if msg is not NULL here,\ninstead of silently ignoring?  Also we didn't see what happened to\nthe promised \"ok\\0ref=...\" stuff here, or the passthru \"ft\".\nPuzzled...\n\n> +\t\t\tcontinue;\n> +\t\t}\n> +\t\toidcpy(&hint->old_oid, &old_oid);\n> +\t\toidcpy(&hint->new_oid, &new_oid);\n> +\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n\nOr, is this last one the catch all for \"ft\" (in other words, the\nhook does not have to say \"ft\", but as long as it says two non-SP\nletters that are not \"ok\" nor \"ng\", it is taken as \"ft\")?\n\n> +\t}\n> +\n> +\tfor (cmd = commands; cmd; cmd = cmd->next)\n> +\t\tif (cmd->run_proc_receive && !cmd->error_string &&\n> +\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {\n> +\t\t    cmd->error_string = \"no report from proc-receive\";\n> +\t\t    code = -1;\n> +\t\t}\n> +\n> +\treturn code;\n> +}\n\nOK, so this sort-of answers my earlier question.  But not quite...\n\nThe output records and the input requests are tied by \"<ref>\" each\ninput record wanted to udpate.  The order does not matter, but not\nhaving a corresponding report in the hook's output is like the hook\nreporting a \"ng\" failure.  It also means that the hook can update\ntwo refs in response to one request, but it is awkward.\n\n> +static int run_proc_receive_hook(struct command *commands,\n> +\t\t\t\t const struct string_list *push_options)\n> +{\n> +\tstruct child_process proc = CHILD_PROCESS_INIT;\n> +\tstruct async muxer;\n> +\tstruct command *cmd;\n> +\tconst char *argv[2];\n> +\tstruct packet_reader reader;\n> +\tstruct strbuf cap = STRBUF_INIT;\n> +\tstruct strbuf errmsg = STRBUF_INIT;\n> +\tint pr_use_push_options = 0;\n> +\tint version = 0;\n> +\tint code;\n> +\n> +\targv[0] = find_hook(\"proc-receive\");\n> +\tif (!argv[0]) {\n> +\t\trp_error(\"cannot find hook 'proc-receive'\");\n> +\t\treturn -1;\n> +\t}\n> +\targv[1] = NULL;\n> +\n> +\tproc.argv = argv;\n> +\tproc.in = -1;\n> +\tproc.out = -1;\n> +\tproc.trace2_hook_name = \"proc-receive\";\n> +\n\nIsn't this a brand new protocol between receive-pack and a hook?  I\nam puzzled why we want a choice between using and not using\nsideband.  It's not like you are maintaining compatibility with\nancient version of Git that talked with proc-receive hook but\nwithout sideband, because it did not know how to multiplex sideband\ncommunication.  Shouldn't we just always assume that the sideband is\navailable?\n\nOr are we just letting the hook directly answer the push client and\nthat is why this thing needs to know if we are using sideband\nbetween us and the client?  I kind of expected that you'd keep the\ntwo communication channels on both sides isolated so that the side\nthat talks with the hook does not need to know how we are talking\nwith the client.\n\n> +\tif (use_sideband) {\n> +\t\tmemset(&muxer, 0, sizeof(muxer));\n> +\t\tmuxer.proc = copy_to_sideband;\n> +\t\tmuxer.in = -1;\n> +\t\tcode = start_async(&muxer);\n> +\t\tif (code)\n> +\t\t\treturn code;\n> +\t\tproc.err = muxer.in;\n> +\t} else {\n> +\t\tproc.err = 0;\n> +\t}\n> +\n> +\tcode = start_command(&proc);\n> +\tif (code) {\n> +\t\tif (use_sideband)\n> +\t\t\tfinish_async(&muxer);\n> +\t\treturn code;\n> +\t}\n> +\n> +\tsigchain_push(SIGPIPE, SIG_IGN);\n> +\n> +\t/* Version negotiaton */\n> +\tpacket_reader_init(&reader, proc.out, NULL, 0,\n> +\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n> +\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n> ...\n> @@ -1497,6 +1731,20 @@ static void execute_commands(struct command *commands,\n>  \n>  \treject_updates_to_hidden(commands);\n>  \n> +\t/* Try to find commands that have special prefix in their reference names,\n\nStyle?\n\n> +\t * and mark them to run an external \"proc-receive\" hook later.\n> +\t */\n"},{"id":"395308","messageId":"xmqq1rose65v.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200412133022.17590-4-worldhello.net@gmail.com","subject":"Re: [PATCH v10 3/8] refs.c: refactor to reuse ref_is_hidden()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-04-12T21:38:20Z","receivedAt":"2020-04-12T21:38:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n>\n> Add new function `ref_is_matched()` to reuse `ref_is_hidden()`. Will use\n\nref_is_matched() may not be wrong per-se, but I would imagine that\nref_matches() would sound more natural.\n\nThe ancient varilabe hide_refs is probably misnamed and should be\nrenamed when the codebase is quiescent to hidden_refs but of course\nwe shouldn't do so before stablizing this series.\n\n"},{"id":"395310","messageId":"xmqqwo6kcr83.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200412133022.17590-5-worldhello.net@gmail.com","subject":"Re: [PATCH v10 4/8] receive-pack: new config receive.procReceiveRefs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-04-12T21:46:20Z","receivedAt":"2020-04-12T21:46:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> +\tif (strcmp(var, \"receive.procreceiverefs\") == 0) {\n> +\t\tchar *prefix;\n> +\t\tint len;\n> +\n> +\t\tif (!value)\n> +\t\t\treturn config_error_nonbool(var);\n> +\t\tprefix = xstrdup(value);\n> +\t\tlen = strlen(prefix);\n> +\t\twhile (len && prefix[len - 1] == '/')\n> +\t\t\tprefix[--len] = '\\0';\n> +\t\tstring_list_insert(&proc_receive_refs, prefix);\n\nThis smells like a copy of the hidden-refs configuration parsing,\nwhich uses string_list_append(), and its result is used for look-up\nroutine you split out in [3/8], so we know it is safe to leave the\nstring_list that holds the prefix unsorted.  Is there a reason why\nwe want to use _insert(), which spends extra cycles to make sure\nthat the string list is sorted, instead (e.g. if we were updating\nthe look-up routine to take advantage of the ordering of the\nstring-list, perhaps)?  If so, would it make sense to update the\noriginal to match?  If not, copy more faithfully to the original and\nuse _append() here, too?\n\nThanks.\n"},{"id":"395323","messageId":"20200413105809.79988-1-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"xmqq5ze4e6j2.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v10 2/8] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-13T10:58:09Z","receivedAt":"2020-04-13T11:04:46Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"\nJunio C Hamano <gitster@pobox.com> 于2020年4月13日周一 上午5:30写道：\n>\n> Jiang Xin <worldhello.net@gmail.com> writes:\n> >     # Receive result from the hook.\n> >     # OK, run this command successfully.\n> >     H: PKT-LINE(<old-oid> <new-oid> <ref> ok)\n> >     # NO, I reject it.\n> >     H: PKT-LINE(<old-oid> <new-oid> <ref> ng <reason>)\n> >     # Fall through, let 'receive-pack' to execute it.\n> >     H: PKT-LINE(<old-oid> <new-oid> <ref> ft)\n> >     # OK, but has an alternate reference.  The alternate reference name\n> >     # and other status are given in key=value pairs after the null\n> >     # character.\n> >     H: PKT-LINE(<old-oid> <new-oid> <ref> ok\\0ref=refs/pull/123/head\n> >                 forced-update)\n>\n> The semantics of this one is fuzzy.  We made an update to a ref that\n> is different from the one that was requested (presumably that is what\n> is reported after \"\\0ref=\"), OK, but did we update the ref in the\n> request, too, or did we leave the ref in the request intact?  Or, do\n> we say \"no\\0ref=...\" if we don't update the requested ref and\n> instead update a different one?  Let's hold the answer at this point\n> and keep thinking...\n\nBefore making a decision, we must consider some limitations or backward\ncompatibility issues.  See the limitations from following code snippet\nof \"send-pack.c\" and \"transport.c\":\n\n* \"receive-pack\" should not send status other than \"ok\" and \"ng\" to \"send-pack\".\n  We must keep this for backward compatibility.\n\n    # File: send-pack.c\n    #\n    152 static int receive_status(struct packet_reader *reader, struct ref *refs)\n    153 {\n    159     while (1) {\n    160         const char *refname;\n    161         char *msg;\n    162         if (packet_reader_read(reader) != PACKET_READ_NORMAL)\n    163             break;\n    164         if (!starts_with(reader->line, \"ok \") && !starts_with(reader->line, \"ng \")) {\n    165             error(\"invalid ref status from remote: %s\", reader->line);\n    166             ret = -1;\n    167             break;\n    168         }\n\n* Do not report unknown reference to \"send-pack\". Each returned reference\n  must be included in `remote_refs`.\n\n    175         /* first try searching at our hint, falling back to all refs */\n    176         if (hint)\n    177             hint = find_ref_by_name(hint, refname);\n    178         if (!hint)\n    179             hint = find_ref_by_name(refs, refname);\n    180       Â  if (!hint) {\n    181             warning(\"remote reported status on unknown ref: %s\",\n    182                     refname);\n\n* Do not report unexpected reference to \"send-pack\".\n\n    185         if (hint->status != REF_STATUS_EXPECTING_REPORT) {\n    186             warning(\"remote reported status on unexpected ref: %s\",\n    187                     refname);\n    188             continue;\n    189         }\n\n* Client will complain if a expecting command not is not received from\n  the report.\n\n    # File: transport.c\n    #\n    531 static int print_one_push_status(struct ref *ref, const char *dest, int count,\n    532                                  int porcelain, int summary_width)\n    540     switch(ref->status) {\n    584     case REF_STATUS_EXPECTING_REPORT:\n    585         print_ref_status('!', \"[remote failure]\", ref,\n    586                          ref->deletion ? NULL : ref->peer_ref,\n    587                          \"remote failed to report status\",\n    588                          porcelain, summary_width);\n\n\nSo we should not invent new status between \"receive-pack\" and \"send-pack\".\nIt is reasonable to to extend the report function from 'receive-pack'\nto 'send-pack' (not the report from 'proc-receive' to 'receive-pack')\nby adding a null character and multiple key-value pairs as extended\nstatus after the \"ok\" and \"ng\" message.\n\nFor example, the following git-push command:\n\n    git push origin HEAD:refs/for/master/topic\n\nThe refspec points to a pseudo reference \"refs/for/master/topic\", which\ndoes not exist on the server, so the old-oid of the command to\n\"receive-pack\" must be a zero-oid. The command for 'proc-receive' is:\n\n    <zero-oid> <new-oid> refs/for/master/topic\n\nIf \"proc-receive\" find this `git-push` command is associated with an already\nexist pull request (such as \"refs/pull/123/head\"), it will update this\nreference instead of creating a new one.\n\nFor this case, we can extend the report line (from \"receive-pack\" to\n\"send-pack”) like this:\n\n    ok refs/for/master/topic\\0ref=refs/pull/123/head old-oid=<hash> [forced-push]\n\nThis extended report line is backward compatible. Old version of git\nclient will show message like this:\n\n    To upstream.git\n     * [new branch]          HEAD -> refs/for/master/topic\n\nNew version of git has the knowledge of how to handle the extended-status\nafter the null character, will print the following message:\n\n    To upstream.git\n     + 1234567...7654321     HEAD -> refs/pull/124/head (forced update)\n\n\nActually, both 'ok' and 'ng' status have the knowledge of '<msg>', even\nthough the '<msg>' is ignored by the 'ok' status. After the extension,\nboth of them can take key-value pairs as extended-status, like:\n\n    ok <reference> [msg]\\0key=value ...\n    ng <reference> [msg]\\0key=value ...\n\nWe can use '<msg>' and the extended status to fit our needs in the future.\n\nAs for the design of status report from 'proc-receive' to 'receive-pack',\nwe should follow the same rules:\n\n1. Never report status for non-exists commands.\n\n2. Do not forget to report some of the commands.\n\n3. After receive report for commands from 'proc-receive', we should have\n   enough information to generate report to 'send-pack' from these commands.\n\nFor the above example, when user push to 'refs/for/master/topic',\n'proc-receive' may report to 'receive-pack' like this:\n\n    PKT-LINE(<old-oid> <new-oid> <ref> ok\\0ref=refs/pull/123/head forced-update)\n\n'send-pack' will save the key-value pairs into the cooresponding `ref->remote_status`.\nIf the report line has a different '<old-oid>' or '<new-oid>' for the '<ref>',\n'send-pack' will substitude the oids of the ref, and append new key-value\npairs (old-oid=<hash> and new-oid=<hash>) to `ref->remote_status`.\n\n> >     H: ... ...\n> >     H: flush-pkt\n> >\n> > After receiving a command, the hook will execute the command, and may\n> > create/update different reference.  For example, a command for a pseudo\n> > reference \"refs/for/master/topic\" may create/update different reference\n> > such as \"refs/pull/123/head\".  The alternate reference name and other\n> > status are given in key-value pairs as extended status of the report\n> > line.\n> >\n> > The list of commands returned from \"proc-receive\" will replace the\n> > relevant commands that are sent from user to \"receive-pack\", and\n> > \"receive-pack\" will continue to run the \"execute_commands\" function and\n> > other routines.  Finally, the result of the execution of these commands\n> > will be reported to end user.\n>\n> It is not clear if there needs to be any correspondence between the\n> sequence of input commands and the sequence of output commands.  I\n> am guessing that there is not any and the hook is allowed to do\n> anything as it wants to.  For example:\n>\n>  - it is OK to execute them all and report them, but in a totally\n>    random order.\n>\n>  - it is OK to ignore all input and report no update (not even\n>    \"ng\").\n>\n>  - it is OK to be asked to update one ref, but update and report\n>    updates to two refs that are not related to the requested ref.\n>\n> Is my understanding correct?\n>\n> Or does the design tie the set of input and output ref updates\n> closely together, i.e. one input corresponds to one output and they\n> are in order, so all the hook is allowed to do is\n>\n>  - to execute and update as it is told, and report \"ok\",\n>\n>  - to reject and report \"ng\", or\n>\n>  - to update *another* ref without updating the requested one, with\n>    \"ok\\0ref=...\" mechanism.\n>\n> I am not sure which one is more sensible, though.\n>\n> If we choose to use the \"anything goes\" approach, I do not think\n> there is no need for the \"ok\\nref=...\" mechanism---we can just give\n> two output records, one \"ok\" for the original request, and a new\n> \"ok\" that reports the \"we updated this one instead\".\n\nI saw you have noticed the code for replacing the commands of 'receive-pack'\nby comparing ref name.\n\n> > +             } else if (strcmp(\"ok\", status)) {\n> > +                     strbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n> > +                                 status, reader->line);\n> > +                     code = -1;\n> > +                     /* Skip marking it as RUN_PROC_RECEIVE_RETURNED */\n>\n> But then shouldn't we be complaining if msg is not NULL here,\n> instead of silently ignoring?  Also we didn't see what happened to\n> the promised \"ok\\0ref=...\" stuff here, or the passthru \"ft\".\n> Puzzled...\n\nI implement \"ft\" and \"\\0key=value...\" features in patch 6/8, and it\ndepends another refactoring commit.  I can squash it to this commit\nin next reroll, and rearrange the refactoring commmit and test cases.\n\n\n> > +                     continue;\n> > +             }\n> > +             oidcpy(&hint->old_oid, &old_oid);\n> > +             oidcpy(&hint->new_oid, &new_oid);\n> > +             hint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n>\n> Or, is this last one the catch all for \"ft\" (in other words, the\n> hook does not have to say \"ft\", but as long as it says two non-SP\n> letters that are not \"ok\" nor \"ng\", it is taken as \"ft\")?\n\n\"ft\" is implemented in patch 6/8.  This code is used to help to find\nmissing reported references.\n\n>\n> > +     }\n> > +\n> > +     for (cmd = commands; cmd; cmd = cmd->next)\n> > +             if (cmd->run_proc_receive && !cmd->error_string &&\n> > +                 !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {\n> > +                 cmd->error_string = \"no report from proc-receive\";\n> > +                 code = -1;\n> > +             }\n> > +\n> > +     return code;\n> > +}\n>\n> OK, so this sort-of answers my earlier question.  But not quite...\n>\n> The output records and the input requests are tied by \"<ref>\" each\n> input record wanted to udpate.  The order does not matter, but not\n> having a corresponding report in the hook's output is like the hook\n> reporting a \"ng\" failure.  It also means that the hook can update\n> two refs in response to one request, but it is awkward.\n>\n\nRight sort order of the output commnands has better performace.\nThe hook can only update commands marked with 'run_proc_receive'.\n\n\"update two refs\", do you mean duplicate report line from 'proc-receive'? \nIf 'proc-receive' really want to update several references for one\ncommand, it must report all of the updates in one report line by adding\nmessage, or adding additional key-value pairs.\n\n> > +static int run_proc_receive_hook(struct command *commands,\n> > +                              const struct string_list *push_options)\n> > +{\n> > +     struct child_process proc = CHILD_PROCESS_INIT;\n> > +     struct async muxer;\n> > +     struct command *cmd;\n> > +     const char *argv[2];\n> > +     struct packet_reader reader;\n> > +     struct strbuf cap = STRBUF_INIT;\n> > +     struct strbuf errmsg = STRBUF_INIT;\n> > +     int pr_use_push_options = 0;\n> > +     int version = 0;\n> > +     int code;\n> > +\n> > +     argv[0] = find_hook(\"proc-receive\");\n> > +     if (!argv[0]) {\n> > +             rp_error(\"cannot find hook 'proc-receive'\");\n> > +             return -1;\n> > +     }\n> > +     argv[1] = NULL;\n> > +\n> > +     proc.argv = argv;\n> > +     proc.in = -1;\n> > +     proc.out = -1;\n> > +     proc.trace2_hook_name = \"proc-receive\";\n> > +\n>\n> Isn't this a brand new protocol between receive-pack and a hook?  I\n> am puzzled why we want a choice between using and not using\n> sideband.  It's not like you are maintaining compatibility with\n> ancient version of Git that talked with proc-receive hook but\n> without sideband, because it did not know how to multiplex sideband\n> communication.  Shouldn't we just always assume that the sideband is\n> available?\n\nThe sideband message stream (stderr of 'proc-receive') will be sent\ndirectly to the client.  If the client is a very old version of git,\nis is still safe?\n\n> Or are we just letting the hook directly answer the push client and\n> that is why this thing needs to know if we are using sideband\n> between us and the client?  I kind of expected that you'd keep the\n> two communication channels on both sides isolated so that the side\n> that talks with the hook does not need to know how we are talking\n> with the client.\n\nIn test cases for 'proc-receive', I output some debug info on stderr\nof 'proc-receive' hook.  If the client (very old version of git)\ndoes not support sideband, it's ok.\n\n> > +     if (use_sideband) {\n> > +             memset(&muxer, 0, sizeof(muxer));\n> > +             muxer.proc = copy_to_sideband;\n> > +             muxer.in = -1;\n> > +             code = start_async(&muxer);\n> > +             if (code)\n> > +                     return code;\n> > +             proc.err = muxer.in;\n> > +     } else {\n> > +             proc.err = 0;\n> > +     }\n> > +\n> > +     code = start_command(&proc);\n> > +     if (code) {\n> > +             if (use_sideband)\n> > +                     finish_async(&muxer);\n> > +             return code;\n> > +     }\n> > +\n> > +     sigchain_push(SIGPIPE, SIG_IGN);\n> > +\n> > +     /* Version negotiaton */\n> > +     packet_reader_init(&reader, proc.out, NULL, 0,\n> > +                        PACKET_READ_CHOMP_NEWLINE |\n> > +                        PACKET_READ_DIE_ON_ERR_PACKET);\n> > ...\n> > @@ -1497,6 +1731,20 @@ static void execute_commands(struct command *commands,\n> >\n> >       reject_updates_to_hidden(commands);\n> >\n> > +     /* Try to find commands that have special prefix in their reference names,\n>\n> Style?\n\nWill fix.\n>\n> > +      * and mark them to run an external \"proc-receive\" hook later.\n> > +      */\n"},{"id":"395324","messageId":"CANYiYbHyLw40sW49QCyMbBEEKgsdpWsScuuWSRAuPJSdxQ2NkQ@mail.gmail.com","threadId":"52936","inReplyTo":"xmqqa73ge9hj.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v10 1/8] transport: not report a non-head push as a branch","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-13T11:15:37Z","receivedAt":"2020-04-13T11:15:51Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2020年4月13日周一 上午4:26写道：\n> > +make_user_friendly_and_stable_output () {\n> > +     make_user_friendly_and_stable_output_common | sed \\\n> > +             -e \"s#To ../upstream.git#To <URL/of/upstream.git>#\"\n>\n> Break the line immediately after the pipe, and begin the next line\n> with the command.  That way, the shell knows that you haven't\n> finished giving the command at the end of the first line, and you do\n> not have to use a backslash, i.e.\n>\n>     make_user_friendly |\n>     sed -e 's/from/to/'\n\nWill do.\n\n> > +             cd workbench &&\n> > +             # Try to make a stable fixed width for abbreviated commit ID,\n> > +             # this fixed-width oid will be replaced with \"<OID>\".\n> > +             git config core.abbrev 7 &&\n> > +             git remote add origin ../upstream.git &&\n> > +             git update-ref refs/heads/master $A &&\n> > +             git tag -m \"v123\" v123 $A &&\n> > +             git push origin \\\n> > +                     $B:refs/heads/master \\\n> > +                     $A:refs/heads/next\n> > +     ) &&\n> > +     TAG=$(git -C workbench rev-parse v123) &&\n> > +\n> > +     # setup pre-receive hook\n> > +     cat >upstream.git/hooks/pre-receive <<-\\EOF &&\n>\n> Use write_script and you don't need the chmod at the end.\n\nYes, it's tedious to write chmod again and again.  Will replace all\nthe places for writing script.\n\n> > +     #!/bin/sh\n> > +\n> > +     echo >&2 \"# pre-receive hook\"\n> > +\n> > +     while read old new ref\n> > +     do\n> > +             echo >&2 \"pre-receive< $old $new $ref\"\n> > +     done\n> > +     EOF\n>\n> Perhaps \"exec >&2\" upfront?\n\nThis works for \"pre-receive\" and \"post-receive\" script.  But in\n\"proc-receive\" hook, stdout and stderr are different, and standard\noutput is used for protocol output.\n\n> > diff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\n> > new file mode 100644\n> > index 0000000000..6e400c0625\n> > --- /dev/null\n> > +++ b/t/t5411/common-functions.sh\n> > @@ -0,0 +1,50 @@\n>\n> It is good that this omits \"#!\" and leaves the file without +x bit.\n>\n> > +# Format the output of git-push, git-show-ref and other commands to make a\n> > +# user-friendly and stable text.  We can easily prepare the expect text\n> > +# without having to worry about future changes of the commit ID and spaces\n> > +# of the output.  We also replce single quotes with double quotes, because\n> > +# it is boring to prepare unquoted single quotes in expect txt.\n>\n> s/txt/text/\n\nWill do. Found it for a long time, but forgot to correct it.\n\n> > +                              (starts_with(ref->name, \"refs/tags/\")\n> > +                               ? \"[new tag]\"\n> > +                               : (starts_with(ref->name, \"refs/heads/\")\n> > +                                  ? \"[new branch]\"\n> > +                                  : \"[new reference]\")),\n> > +                              ref, ref->peer_ref, NULL, porcelain, summary_width);\n>\n> Now we say \"outside refs/heads/ and refs/tags/, that's just a\n> reference, different from branch\".  OK.\n>\n> Do we ever update refs/remotes/ with this codepath?  Would we want\n> to label it differently, i.e. as \"remote-tracking branch\"?\n\nCan we call a reference (starts with \"refs/remotes/\") as a\nremote-tracking branch, if it is on the remote side without a proper\nremote and branch settings in .git/config?\n\nThanks.\n"},{"id":"395325","messageId":"CANYiYbFkO9H01JGwFWzqdpo7jFMU8VMNh-0H7sLz6kd8x8_Xjw@mail.gmail.com","threadId":"52936","inReplyTo":"xmqq1rose65v.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v10 3/8] refs.c: refactor to reuse ref_is_hidden()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-13T11:16:18Z","receivedAt":"2020-04-13T11:16:31Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2020年4月13日周一 上午5:38写道：\n>\n> Jiang Xin <worldhello.net@gmail.com> writes:\n>\n> > From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n> >\n> > Add new function `ref_is_matched()` to reuse `ref_is_hidden()`. Will use\n>\n> ref_is_matched() may not be wrong per-se, but I would imagine that\n> ref_matches() would sound more natural.\n\nWill do.\n\n\n>\n> The ancient varilabe hide_refs is probably misnamed and should be\n> renamed when the codebase is quiescent to hidden_refs but of course\n> we shouldn't do so before stablizing this series.\n>\n"},{"id":"395326","messageId":"CANYiYbGDxA62wUhaAZYzzHnhjkTppQjPL-Q=xQZtJ6Rdj0fbEg@mail.gmail.com","threadId":"52936","inReplyTo":"xmqqwo6kcr83.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v10 4/8] receive-pack: new config receive.procReceiveRefs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-13T11:16:36Z","receivedAt":"2020-04-13T11:16:50Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2020年4月13日周一 上午5:46写道：\n>\n> Jiang Xin <worldhello.net@gmail.com> writes:\n>\n> > +     if (strcmp(var, \"receive.procreceiverefs\") == 0) {\n> > +             char *prefix;\n> > +             int len;\n> > +\n> > +             if (!value)\n> > +                     return config_error_nonbool(var);\n> > +             prefix = xstrdup(value);\n> > +             len = strlen(prefix);\n> > +             while (len && prefix[len - 1] == '/')\n> > +                     prefix[--len] = '\\0';\n> > +             string_list_insert(&proc_receive_refs, prefix);\n>\n> This smells like a copy of the hidden-refs configuration parsing,\n> which uses string_list_append(), and its result is used for look-up\n\nSo this means I copied it from elsewhere ;-)\nWill replace it from string_list_insert()  to string_list_append().\n"},{"id":"395353","messageId":"20200413164828.23260-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200412133022.17590-1-worldhello.net@gmail.com","subject":"[PATCH v11 0/7] New proc-receive hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-13T16:48:20Z","receivedAt":"2020-04-13T16:48:36Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\n## Changes since v10\n\n* Patch 3/7 of v11 is a squash commit of patch 2/8 and patch 6/8 of v10.\n\n* Fixed issues discovered for code review of v10.\n\n## Range-diff v10...v11\n\n1:  a293077bcf ! 1:  77a731c151 transport: not report a non-head push as a branch\n    @@ t/t5411-proc-receive-hook.sh (new)\n     +# we also replace the URL of different protocol for the upstream repository\n     +# with a fixed pattern.\n     +make_user_friendly_and_stable_output () {\n    -+\tmake_user_friendly_and_stable_output_common | sed \\\n    -+\t\t-e \"s#To ../upstream.git#To <URL/of/upstream.git>#\"\n    ++\tmake_user_friendly_and_stable_output_common |\n    ++\t\tsed -e \"s#To ../upstream.git#To <URL/of/upstream.git>#\"\n     +}\n     +\n     +# Refs of upstream : master(B)  next(A)\n    @@ t/t5411-proc-receive-hook.sh (new)\n     +\tTAG=$(git -C workbench rev-parse v123) &&\n     +\n     +\t# setup pre-receive hook\n    -+\tcat >upstream.git/hooks/pre-receive <<-\\EOF &&\n    -+\t#!/bin/sh\n    -+\n    -+\techo >&2 \"# pre-receive hook\"\n    -+\n    ++\twrite_script upstream.git/hooks/pre-receive <<-\\EOF &&\n    ++\texec >&2\n    ++\techo \"# pre-receive hook\"\n     +\twhile read old new ref\n     +\tdo\n    -+\t\techo >&2 \"pre-receive< $old $new $ref\"\n    ++\t\techo \"pre-receive< $old $new $ref\"\n     +\tdone\n     +\tEOF\n     +\n     +\t# setup post-receive hook\n    -+\tcat >upstream.git/hooks/post-receive <<-\\EOF &&\n    -+\t#!/bin/sh\n    -+\n    -+\techo >&2 \"# post-receive hook\"\n    -+\n    ++\twrite_script upstream.git/hooks/post-receive <<-\\EOF &&\n    ++\texec >&2\n    ++\techo \"# post-receive hook\"\n     +\twhile read old new ref\n     +\tdo\n    -+\t\techo >&2 \"post-receive< $old $new $ref\"\n    ++\t\techo \"post-receive< $old $new $ref\"\n     +\tdone\n     +\tEOF\n     +\n    -+\tchmod a+x \\\n    -+\t\tupstream.git/hooks/pre-receive \\\n    -+\t\tupstream.git/hooks/post-receive &&\n    -+\n     +\tupstream=upstream.git\n     +'\n     +\n    @@ t/t5411/common-functions.sh (new)\n     +# user-friendly and stable text.  We can easily prepare the expect text\n     +# without having to worry about future changes of the commit ID and spaces\n     +# of the output.  We also replce single quotes with double quotes, because\n    -+# it is boring to prepare unquoted single quotes in expect txt.\n    ++# it is boring to prepare unquoted single quotes in expect text.\n     +make_user_friendly_and_stable_output_common () {\n     +\tsed \\\n     +\t\t-e \"s/  *\\$//\" \\\n    @@ t/t5412-proc-receive-hook-http-protocol.sh (new)\n     +# we also replace the URL of different protocol for the upstream repository\n     +# with a fixed pattern.\n     +make_user_friendly_and_stable_output () {\n    -+\tmake_user_friendly_and_stable_output_common | sed \\\n    -+\t\t-e \"s#To http:.*/upstream.git#To <URL/of/upstream.git>#\"\n    ++\tmake_user_friendly_and_stable_output_common |\n    ++\t\tsed -e \"s#To http:.*/upstream.git#To <URL/of/upstream.git>#\"\n     +}\n     +\n     +# Refs of upstream : master(B)  next(A)\n    @@ t/t5412-proc-receive-hook-http-protocol.sh (new)\n     +\tTAG=$(git -C workbench rev-parse v123) &&\n     +\n     +\t# setup pre-receive hook\n    -+\tcat >upstream.git/hooks/pre-receive <<-\\EOF &&\n    -+\t#!/bin/sh\n    -+\n    -+\techo >&2 \"# pre-receive hook\"\n    -+\n    ++\twrite_script upstream.git/hooks/pre-receive <<-\\EOF &&\n    ++\texec >&2\n    ++\techo \"# pre-receive hook\"\n     +\twhile read old new ref\n     +\tdo\n    -+\t\techo >&2 \"pre-receive< $old $new $ref\"\n    ++\t\techo \"pre-receive< $old $new $ref\"\n     +\tdone\n     +\tEOF\n     +\n     +\t# setup post-receive hook\n    -+\tcat >upstream.git/hooks/post-receive <<-\\EOF &&\n    -+\t#!/bin/sh\n    -+\n    -+\techo >&2 \"# post-receive hook\"\n    -+\n    ++\twrite_script upstream.git/hooks/post-receive <<-\\EOF &&\n    ++\texec >&2\n    ++\techo \"# post-receive hook\"\n     +\twhile read old new ref\n     +\tdo\n    -+\t\techo >&2 \"post-receive< $old $new $ref\"\n    ++\t\techo \"post-receive< $old $new $ref\"\n     +\tdone\n     +\tEOF\n     +\n    -+\tchmod a+x \\\n    -+\t\tupstream.git/hooks/pre-receive \\\n    -+\t\tupstream.git/hooks/post-receive &&\n    -+\n     +\tupstream=\"$HTTPD_DOCUMENT_ROOT_PATH/upstream.git\" &&\n     +\tmv upstream.git \"$upstream\" &&\n     +\tgit -C workbench remote set-url origin $HTTPD_URL/smart/upstream.git\n5:  75b178fc19 = 2:  4c4f7cffff connect: export parse_feature_value()\n2:  915b9ef616 ! 3:  bef4360e58 receive-pack: add new proc-receive hook\n    @@ Commit message\n         other routines.  Finally, the result of the execution of these commands\n         will be reported to end user.\n     \n    +    The reporting function from \"receive-pack\" to \"send-pack\" is also\n    +    extended using a backward compatible way by adding key-value pairs\n    +    after an null character, like:\n    +\n    +        # OK, run this command successfully with optional extended-status.\n    +        ok <reference>\\0ref=refs/pull/123/head old-oid=...\n    +\n    +        # NO, I reject it.\n    +        ng <reference> <error message>\n    +\n    +    Parsing the extended status of the report lines for \"send-pack\" is not\n    +    implemented in this commit, so we can make a compatible test on old\n    +    version of a Git client.\n    +\n         Suggested-by: Junio C Hamano <gitster@pobox.com>\n         Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n     \n    @@ builtin/receive-pack.c: static void write_head_info(void)\n      struct command {\n      \tstruct command *next;\n      \tconst char *error_string;\n    ++\tconst char *extended_status;\n      \tunsigned int skip_update:1,\n     -\t\t     did_not_exist:1;\n     +\t\t     did_not_exist:1,\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +{\n     +\tstruct command *cmd;\n     +\tstruct command *hint = NULL;\n    ++\tstruct strbuf extended_status = STRBUF_INIT;\n     +\tint code = 0;\n     +\n     +\tfor (;;) {\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\tconst char *p;\n     +\t\tchar *status;\n     +\t\tchar *msg = NULL;\n    ++\t\tint len;\n     +\n     +\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n     +\t\t\tbreak;\n    ++\t\tlen = strlen(reader->line);\n     +\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n     +\t\t    *p++ != ' ' ||\n     +\t\t    parse_oid_hex(p, &new_oid, &p) ||\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\t\tcontinue;\n     +\t\t}\n     +\t\t*status++ = '\\0';\n    -+\t\tif (strlen(status) > 2 && *(status + 2) == ' ') {\n    -+\t\t\tmsg = status + 2;\n    ++\t\tmsg = strchr(status, ' ');\n    ++\t\tif (msg)\n     +\t\t\t*msg++ = '\\0';\n    -+\t\t}\n    -+\t\tif (strlen(status) != 2) {\n    -+\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n    -+\t\t\t\t    status, reader->line);\n    -+\t\t\tcode = -1;\n    -+\t\t\tcontinue;\n    -+\t\t}\n     +\n     +\t\t/* first try searching at our hint, falling back to all refs */\n     +\t\tif (hint)\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\t\tcontinue;\n     +\t\t}\n     +\t\tif (!hint->run_proc_receive) {\n    -+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on ref of builtin command: %s\\n\",\n    ++\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unexpected ref: %s\\n\",\n     +\t\t\t\t    refname);\n     +\t\t\tcontinue;\n     +\t\t}\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\t\telse\n     +\t\t\t\thint->error_string = \"failed\";\n     +\t\t\tcode = -1;\n    ++\t\t} else if (!strcmp(\"ft\", status)) {\n    ++\t\t\t/* Reset \"run_proc_receive\" field, and continue to run in \"receive-pack\" */\n    ++\t\t\thint->run_proc_receive = 0;\n     +\t\t} else if (strcmp(\"ok\", status)) {\n    -+\t\t\tstrbuf_addf(errmsg, \"proc-receive has bad status '%s' for '%s'\\n\",\n    -+\t\t\t\t    status, reader->line);\n    ++\t\t\tstrbuf_addf(errmsg, \"proc-receive reported bad status '%s' on ref '%s'\\n\",\n    ++\t\t\t\t    status, refname);\n     +\t\t\tcode = -1;\n     +\t\t\t/* Skip marking it as RUN_PROC_RECEIVE_RETURNED */\n     +\t\t\tcontinue;\n     +\t\t}\n    -+\t\toidcpy(&hint->old_oid, &old_oid);\n    -+\t\toidcpy(&hint->new_oid, &new_oid);\n    -+\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n    ++\t\tif (reader->pktlen > len)\n    ++\t\t\tstrbuf_addstr(&extended_status, (char *)reader->line + len + 1);\n    ++\t\tif (oidcmp(&hint->old_oid, &old_oid)) {\n    ++\t\t\toidcpy(&hint->old_oid, &old_oid);\n    ++\t\t\tstrbuf_addf(&extended_status, \"%sold-oid=%s\",\n    ++\t\t\t\t    extended_status.len > 0 ? \" \": \"\",\n    ++\t\t\t\t    oid_to_hex(&old_oid));\n    ++\t\t}\n    ++\t\tif (oidcmp(&hint->new_oid, &new_oid)) {\n    ++\t\t\toidcpy(&hint->new_oid, &new_oid);\n    ++\t\t\tstrbuf_addf(&extended_status, \"%snew-oid=%s\",\n    ++\t\t\t\t    extended_status.len > 0 ? \" \": \"\",\n    ++\t\t\t\t    oid_to_hex(&new_oid));\n    ++\t\t}\n    ++\t\tif (extended_status.len > 0)\n    ++\t\t\thint->extended_status = strbuf_detach(&extended_status, NULL);\n    ++\t\tif (hint->run_proc_receive)\n    ++\t\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n     +\t}\n     +\n     +\tfor (cmd = commands; cmd; cmd = cmd->next)\n     +\t\tif (cmd->run_proc_receive && !cmd->error_string &&\n     +\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {\n    -+\t\t    cmd->error_string = \"no report from proc-receive\";\n    ++\t\t    cmd->error_string = \"proc-receive failed to report status\";\n     +\t\t    code = -1;\n     +\t\t}\n     +\n    @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n      \n      \treject_updates_to_hidden(commands);\n      \n    -+\t/* Try to find commands that have special prefix in their reference names,\n    ++\t/*\n    ++\t * Try to find commands that have special prefix in their reference names,\n     +\t * and mark them to run an external \"proc-receive\" hook later.\n     +\t */\n     +\tfor (cmd = commands; cmd; cmd = cmd->next) {\n    @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n      \tif (use_atomic)\n      \t\texecute_commands_atomic(commands, si);\n      \telse\n    +@@ builtin/receive-pack.c: static void report(struct command *commands, const char *unpack_status)\n    + \tpacket_buf_write(&buf, \"unpack %s\\n\",\n    + \t\t\t unpack_status ? unpack_status : \"ok\");\n    + \tfor (cmd = commands; cmd; cmd = cmd->next) {\n    +-\t\tif (!cmd->error_string)\n    +-\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n    +-\t\t\t\t\t cmd->ref_name);\n    +-\t\telse\n    +-\t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n    +-\t\t\t\t\t cmd->ref_name, cmd->error_string);\n    ++\t\tif (!cmd->error_string) {\n    ++\t\t\tif (cmd->extended_status)\n    ++\t\t\t\tpacket_buf_write(&buf, \"ok %s%c%s\\n\",\n    ++\t\t\t\t\t\t cmd->ref_name, '\\0',\n    ++\t\t\t\t\t\t cmd->extended_status);\n    ++\t\t\telse\n    ++\t\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n    ++\t\t\t\t\t\t cmd->ref_name);\n    ++\t\t} else {\n    ++\t\t\tif (cmd->extended_status)\n    ++\t\t\t\tpacket_buf_write(&buf, \"ng %s %s%c%s\\n\",\n    ++\t\t\t\t\t\t cmd->ref_name, cmd->error_string,\n    ++\t\t\t\t\t\t '\\0', cmd->extended_status);\n    ++\t\t\telse\n    ++\t\t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n    ++\t\t\t\t\t\t cmd->ref_name, cmd->error_string);\n    ++\t\t}\n    + \t}\n    + \tpacket_buf_flush(&buf);\n    + \n     \n      ## t/helper/test-proc-receive.c (new) ##\n     @@\n    @@ t/helper/test-proc-receive.c (new)\n     +\t}\n     +\n     +\tif (returns.nr)\n    -+\t\tfor_each_string_list_item(item, &returns)\n    -+\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n    ++\t\tfor_each_string_list_item(item, &returns) {\n    ++\t\t\tchar *p;\n    ++\n    ++\t\t\tp = strstr(item->string, \"\\\\0\");\n    ++\t\t\tif (p) {\n    ++\t\t\t\t*p = '\\0';\n    ++\t\t\t\tp += 2;\n    ++\t\t\t\tpacket_write_fmt(1, \"%s%c%s\\n\", item->string, '\\0', p);\n    ++\t\t\t} else {\n    ++\t\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n    ++\t\t\t}\n    ++\t\t}\n     +\tpacket_flush(1);\n     +\tsigchain_pop(SIGPIPE);\n     +\n    @@ t/t5411/common-functions.sh: create_commits_in () {\n      # user-friendly and stable text.  We can easily prepare the expect text\n      # without having to worry about future changes of the commit ID and spaces\n     -# of the output.  We also replce single quotes with double quotes, because\n    --# it is boring to prepare unquoted single quotes in expect txt.\n    +-# it is boring to prepare unquoted single quotes in expect text.\n     +# of the output.  Single quotes are replaced with double quotes, because\n    -+# it is boring to prepare unquoted single quotes in expect txt.  We also\n    ++# it is boring to prepare unquoted single quotes in expect text.  We also\n     +# remove some locale error messages, which break test if we turn on\n     +# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n     +# on plumbing commands.\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       next(A)  refs/for/master/topic(A)\n    -+test_expect_success \"no proc-receive hook, fail to push special ref\" '\n    ++test_expect_success \"proc-receive: no hook, fail to push special ref\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:next \\\n     +\t\tHEAD:refs/for/master/topic \\\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n    -+test_expect_success \"no proc-receive hook, fail all for atomic push\" '\n    ++test_expect_success \"proc-receive: no hook, fail all for atomic push\" '\n     +\ttest_must_fail git -C workbench push --atomic origin \\\n     +\t\t$B:master \\\n     +\t\tHEAD:next \\\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (bad version)\" '\n    -+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n    -+\t#!/bin/sh\n    -+\n    ++test_expect_success \"setup proc-receive hook (unknown version)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    -+\n     +\ttest-tool proc-receive -v --version 2\n     +\tEOF\n    -+\tchmod a+x \"$upstream/hooks/proc-receive\"\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive bad protocol: unknown version\" '\n    ++test_expect_success \"proc-receive: bad protocol (unknown version)\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook (no report)\" '\n    -+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n    -+\t#!/bin/sh\n    -+\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    -+\n     +\ttest-tool proc-receive -v\n     +\tEOF\n     +'\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       next(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive bad protocol: no report\" '\n    ++test_expect_success \"proc-receive: bad protocol (no report)\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:refs/heads/next \\\n     +\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n     +\tTo <URL/of/upstream.git>\n     +\t * [new branch] HEAD -> next\n    -+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n    ++\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n     +\tEOF\n     +\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook (bad oid)\" '\n    -+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n    -+\t#!/bin/sh\n    -+\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    -+\n     +\ttest-tool proc-receive -v \\\n     +\t\t-r \"bad-id new-id ref ok\"\n     +\tEOF\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic\n    -+test_expect_success \"proc-receive bad protocol: bad oid\" '\n    ++test_expect_success \"proc-receive: bad protocol (bad oid)\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic\\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: proc-receive> bad-id new-id ref ok\n     +\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"bad-id new-id ref ok\"\n     +\tTo <URL/of/upstream.git>\n    -+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n    ++\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n     +\tEOF\n     +\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook (no status)\" '\n    -+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n    -+\t#!/bin/sh\n    -+\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    -+\n     +\ttest-tool proc-receive -v \\\n     +\t\t-r \"$ZERO_OID $A refs/for/master/topic\"\n     +\tEOF\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic\n    -+test_expect_success \"proc-receive bad protocol: no status\" '\n    ++test_expect_success \"proc-receive: bad protocol (no status)\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n     +\tTo <URL/of/upstream.git>\n    -+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n    ++\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n     +\tEOF\n     +\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook (unknown status)\" '\n    -+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n    -+\t#!/bin/sh\n    -+\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    -+\n     +\ttest-tool proc-receive -v \\\n     +\t\t-r \"$ZERO_OID $A refs/for/master/topic xx msg\"\n     +\tEOF\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic\n    -+test_expect_success \"proc-receive bad protocol: unknown status\" '\n    ++test_expect_success \"proc-receive: bad protocol (unknown status)\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\t\tHEAD:refs/for/master/topic \\\n     +\t\t\t>out 2>&1 &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic xx msg\n    -+\tremote: error: proc-receive has bad status \"xx\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n    ++\tremote: error: proc-receive reported bad status \"xx\" on ref \"refs/for/master/topic\"\n     +\tTo <URL/of/upstream.git>\n    -+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n    -+\tEOF\n    -+\ttest_cmp expect actual &&\n    -+\tgit -C \"$upstream\" show-ref >out &&\n    -+\tmake_user_friendly_and_stable_output <out >actual &&\n    -+\tcat >expect <<-EOF &&\n    -+\t<COMMIT-A> refs/heads/master\n    -+\tEOF\n    -+\ttest_cmp expect actual\n    -+'\n    -+\n    -+test_expect_success \"setup proc-receive hook (bad status)\" '\n    -+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n    -+\t#!/bin/sh\n    -+\n    -+\tprintf >&2 \"# proc-receive hook\\n\"\n    -+\n    -+\ttest-tool proc-receive -v \\\n    -+\t\t-r \"$ZERO_OID $A refs/for/master/topic bad status\"\n    -+\tEOF\n    -+'\n    -+\n    -+# Refs of upstream : master(A)\n    -+# Refs of workbench: master(A)  tags/v123\n    -+# git push         :                       refs/for/master/topic\n    -+test_expect_success \"proc-receive bad protocol: bad status\" '\n    -+\ttest_must_fail git -C workbench push origin \\\n    -+\t\tHEAD:refs/for/master/topic \\\n    -+\t\t>out 2>&1 &&\n    -+\tmake_user_friendly_and_stable_output <out >actual &&\n    -+\tcat >expect <<-EOF &&\n    -+\tremote: # pre-receive hook\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: # proc-receive hook\n    -+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic bad status\n    -+\tremote: error: proc-receive has bad status \"bad status\" for \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n    -+\tTo <URL/of/upstream.git>\n    -+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n    ++\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n     +\tEOF\n     +\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook (ng)\" '\n    -+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n    -+\t#!/bin/sh\n    -+\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    -+\n     +\ttest-tool proc-receive -v \\\n     +\t\t-r \"$ZERO_OID $A refs/for/master/topic ng\"\n     +\tEOF\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic\n    -+test_expect_success \"proc-receive: fail to update (no message)\" '\n    ++test_expect_success \"proc-receive: fail to update (ng)\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook (ng message)\" '\n    -+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n    -+\t#!/bin/sh\n    -+\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    -+\n     +\ttest-tool proc-receive -v \\\n     +\t\t-r \"$ZERO_OID $A refs/for/master/topic ng error msg\"\n     +\tEOF\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic\n    -+test_expect_success \"proc-receive: fail to update (has message)\" '\n    ++test_expect_success \"proc-receive: fail to update (ng message)\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (report status on builtin command)\" '\n    -+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n    -+\t#!/bin/sh\n    -+\n    ++test_expect_success \"setup proc-receive hook (unexpected ref)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    -+\n     +\ttest-tool proc-receive -v \\\n     +\t\t-r \"$ZERO_OID $A refs/heads/master ok\"\n     +\tEOF\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         : (B)                   refs/for/master/topic\n    -+test_expect_success \"proc-receive: warning on report for builtin command\" '\n    ++test_expect_success \"proc-receive: report on unexpected ref\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\t$B:refs/heads/master \\\n     +\t\tHEAD:refs/for/master/topic \\\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/heads/master ok\n    -+\tremote: error: proc-receive reported status on ref of builtin command: refs/heads/master\n    ++\tremote: error: proc-receive reported status on unexpected ref: refs/heads/master\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n     +\tTo <URL/of/upstream.git>\n     +\t <OID-A>..<OID-B> <COMMIT-B> -> master\n    -+\t ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)\n    ++\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n     +\tEOF\n     +\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\ttest_cmp expect actual\n     +'\n     +\n    ++# Refs of upstream : master(B)\n    ++# Refs of workbench: master(A)  tags/v123\n     +test_expect_success \"cleanup\" '\n     +\tgit -C \"$upstream\" update-ref refs/heads/master $A\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook (ok)\" '\n    -+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF\n    -+\t#!/bin/sh\n    -+\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    -+\n     +\ttest-tool proc-receive -v \\\n     +\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n     +\tEOF\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/a/b/c/my/topic\n    -+test_expect_success \"proc-receive: no report from proc-receive\" '\n    ++test_expect_success \"proc-receive: failed to report status\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:refs/for/a/b/c/my/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n     +\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    -+\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (no report from proc-receive)\n    ++\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (proc-receive failed to report status)\n     +\tEOF\n     +\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push -o ...  :                       refs/for/master/topic\n    -+test_expect_success \"not support push options\" '\n    ++test_expect_success \"proc-receive: not support push options\" '\n     +\ttest_must_fail git -C workbench push \\\n     +\t\t-o issue=123 \\\n     +\t\t-o reviewer=user1 \\\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push -o ...  :                       next(A)  refs/for/master/topic\n    -+test_expect_success \"push with options\" '\n    ++test_expect_success \"proc-receive: push with options\" '\n     +\tgit -C workbench push \\\n     +\t\t--atomic \\\n     +\t\t-o issue=123 \\\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tEOF\n     +\ttest_cmp expect actual\n     +'\n    ++\n    ++# Refs of upstream : master(A)             next(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++test_expect_success \"cleanup\" '\n    ++\tgit -C \"$upstream\" update-ref -d refs/heads/next\n    ++'\n    ++\n    ++test_expect_success \"setup proc-receive hook (ft)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"$ZERO_OID $B refs/for/master/topic ft\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/master/topic(B)\n    ++test_expect_success \"proc-receive: fall throught, let receive-pack to execute\" '\n    ++\tgit -C workbench push origin \\\n    ++\t\t$B:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n    ++\tremote: proc-receive> <ZERO-OID> <COMMIT-B> refs/for/master/topic ft\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t * [new reference] <COMMIT-B> -> refs/for/master/topic\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-B> refs/for/master/topic\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++# Refs of upstream : master(A)             refs/for/master/topic(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++test_expect_success \"cleanup\" '\n    ++\tgit -C \"$upstream\" update-ref -d refs/for/master/topic\n    ++'\n    ++\n    ++test_expect_success \"setup proc-receive hook (with extended status)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"$ZERO_OID $A refs/for/a/b/c/topic ok\" \\\n    ++\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\\0ref=refs/pull/123/head\" \\\n    ++\t\t-r \"$B $A refs/for/master/topic ok\\0ref=refs/pull/124/head forced-update\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report with extended status\" '\n    ++\tgit -C workbench push origin \\\n    ++\t\tHEAD:refs/for/next/topic \\\n    ++\t\tHEAD:refs/for/a/b/c/topic \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic ok\n    ++\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\\0ref=refs/pull/123/head\n    ++\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/master/topic ok\\0ref=refs/pull/124/head forced-update\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n    ++\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t * [new reference] HEAD -> refs/for/next/topic\n    ++\t * [new reference] HEAD -> refs/for/a/b/c/topic\n    ++\t * [new reference] HEAD -> refs/for/master/topic\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++test_expect_success \"setup proc-receive hook\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n    ++\t\t-r \"$A $B refs/for/master/topic ok\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report update of mixed refs\" '\n    ++\tgit -C workbench push origin \\\n    ++\t\t$B:refs/heads/master \\\n    ++\t\tHEAD:refs/heads/bar \\\n    ++\t\tHEAD:refs/heads/baz \\\n    ++\t\tHEAD:refs/for/next/topic \\\n    ++\t\tHEAD:refs/heads/foo \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n    ++\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/for/master/topic ok\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n    ++\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t <OID-A>..<OID-B> <COMMIT-B> -> master\n    ++\t * [new branch] HEAD -> bar\n    ++\t * [new branch] HEAD -> baz\n    ++\t * [new reference] HEAD -> refs/for/next/topic\n    ++\t * [new branch] HEAD -> foo\n    ++\t * [new reference] HEAD -> refs/for/master/topic\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/bar\n    ++\t<COMMIT-A> refs/heads/baz\n    ++\t<COMMIT-A> refs/heads/foo\n    ++\t<COMMIT-B> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n3:  16257616ec < -:  ---------- refs.c: refactor to reuse ref_is_hidden()\n7:  d1677b61d1 ! 4:  a4a1cc1d14 send-pack: extension for client-side status report\n    @@ send-pack.c: static int receive_status(struct packet_reader *reader, struct ref\n      \t}\n     \n      ## t/t5411/common-test-cases.sh ##\n    -@@ t/t5411/common-test-cases.sh: test_expect_success \"report update of all special refs\" '\n    - \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n    +@@ t/t5411/common-test-cases.sh: test_expect_success \"proc-receive: report with extended status\" '\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n      \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n      \tTo <URL/of/upstream.git>\n     -\t * [new reference] HEAD -> refs/for/next/topic\n     +\t * [new reference] HEAD -> refs/pull/123/head\n    - \t * [new reference] HEAD -> refs/review/a/b/c/topic\n    + \t * [new reference] HEAD -> refs/for/a/b/c/topic\n     -\t * [new reference] HEAD -> refs/for/master/topic\n     +\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n      \tEOF\n      \ttest_cmp expect actual &&\n      \tgit -C \"$upstream\" show-ref >out &&\n    -@@ t/t5411/common-test-cases.sh: test_expect_success \"report mixed refs update\" '\n    +@@ t/t5411/common-test-cases.sh: test_expect_success \"proc-receive: report update of mixed refs\" '\n      \t * [new branch] HEAD -> baz\n      \t * [new reference] HEAD -> refs/for/next/topic\n      \t * [new branch] HEAD -> foo\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"report mixed refs update\" '\n      \tEOF\n      \ttest_cmp expect actual &&\n      \tgit -C \"$upstream\" show-ref >out &&\n    -@@ t/t5411/common-test-cases.sh: test_expect_success \"report test: fallthrough\" '\n    - \t <OID-A>..<OID-B> <COMMIT-B> -> bar\n    - \t - [deleted] foo\n    - \t + <OID-B>...<OID-A> HEAD -> master (forced update)\n    --\t * [new reference] HEAD -> refs/for/master/topic\n    --\t * [new reference] HEAD -> refs/for/next/topic\n    -+\t <OID-A>..<OID-B> HEAD -> refs/pull/123/head\n    -+\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n    - \tEOF\n    - \ttest_cmp expect actual &&\n    - \tgit -C \"$upstream\" show-ref >out &&\n     \n      ## transport-helper.c ##\n     @@ transport-helper.c: static int push_update_ref_status(struct strbuf *buf,\n-:  ---------- > 5:  4e3c383c12 refs.c: refactor to reuse ref_is_hidden()\n4:  cd49b57c17 ! 6:  dd83569c4e receive-pack: new config receive.procReceiveRefs\n    @@ builtin/receive-pack.c: static int receive_pack_config(const char *var, const ch\n     +\t\tlen = strlen(prefix);\n     +\t\twhile (len && prefix[len - 1] == '/')\n     +\t\t\tprefix[--len] = '\\0';\n    -+\t\tstring_list_insert(&proc_receive_refs, prefix);\n    ++\t\tstring_list_append(&proc_receive_refs, prefix);\n     +\t\treturn 0;\n     +\t}\n     +\n    @@ builtin/receive-pack.c: static int receive_pack_config(const char *var, const ch\n      }\n      \n     @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n    - \t/* Try to find commands that have special prefix in their reference names,\n    + \t * Try to find commands that have special prefix in their reference names,\n      \t * and mark them to run an external \"proc-receive\" hook later.\n      \t */\n     -\tfor (cmd = commands; cmd; cmd = cmd->next) {\n    @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n     +\tif (proc_receive_refs.nr > 0) {\n     +\t\tstruct strbuf refname_full = STRBUF_INIT;\n     +\t\tsize_t prefix_len;\n    -+\n    -+\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n    -+\t\tprefix_len = refname_full.len;\n      \n     -\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n     -\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n     -\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n     -\t\t\trun_proc_receive = 1;\n    ++\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n    ++\t\tprefix_len = refname_full.len;\n    ++\n     +\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n     +\t\t\tif (!should_process_cmd(cmd))\n     +\t\t\t\tcontinue;\n     +\n     +\t\t\tstrbuf_setlen(&refname_full, prefix_len);\n     +\t\t\tstrbuf_addstr(&refname_full, cmd->ref_name);\n    -+\t\t\tif (ref_is_matched(&proc_receive_refs, cmd->ref_name, refname_full.buf)) {\n    ++\t\t\tif (ref_matches(&proc_receive_refs, cmd->ref_name, refname_full.buf)) {\n     +\t\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n     +\t\t\t\trun_proc_receive = 1;\n     +\t\t\t}\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"cleanup\" '\n      # Refs of upstream : master(A)\n      # Refs of workbench: master(A)  tags/v123\n      # git push         :                       next(A)  refs/for/master/topic(A)\n    -@@ t/t5411/common-test-cases.sh: test_expect_success \"push with options\" '\n    - \tEOF\n    - \ttest_cmp expect actual\n    - '\n    -+\n    -+# Refs of upstream : master(A)             next(A)\n    -+# Refs of workbench: master(A)  tags/v123\n    -+test_expect_success \"cleanup\" '\n    -+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n    -+'\n    -+\n    -+test_expect_success \"setup proc-receive hook\" '\n    -+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n    -+\t#!/bin/sh\n    -+\n    -+\tprintf >&2 \"# proc-receive hook\\n\"\n    -+\n    -+\ttest-tool proc-receive -v \\\n    +@@ t/t5411/common-test-cases.sh: test_expect_success \"setup proc-receive hook (with extended status)\" '\n    + \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    + \tprintf >&2 \"# proc-receive hook\\n\"\n    + \ttest-tool proc-receive -v \\\n    +-\t\t-r \"$ZERO_OID $A refs/for/a/b/c/topic ok\" \\\n     +\t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n    -+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n    -+\t\t-r \"$B $A refs/for/master/topic ok\"\n    -+\tEOF\n    -+\tchmod a+x \"$upstream/hooks/proc-receive\"\n    -+'\n    -+\n    -+# Refs of upstream : master(A)\n    -+# Refs of workbench: master(A)  tags/v123\n    + \t\t-r \"$ZERO_OID $A refs/for/next/topic ok\\0ref=refs/pull/123/head\" \\\n    + \t\t-r \"$B $A refs/for/master/topic ok\\0ref=refs/pull/124/head forced-update\"\n    + \tEOF\n    +@@ t/t5411/common-test-cases.sh: test_expect_success \"setup proc-receive hook (with extended status)\" '\n    + \n    + # Refs of upstream : master(A)\n    + # Refs of workbench: master(A)  tags/v123\n    +-# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n     +# git push         :                       refs/for/next/topic(A)  refs/review/a/b/c/topic(A)  refs/for/master/topic(A)\n    -+test_expect_success \"report update of all special refs\" '\n    -+\tgit -C workbench push origin \\\n    -+\t\tHEAD:refs/for/next/topic \\\n    + test_expect_success \"proc-receive: report with extended status\" '\n    + \tgit -C workbench push origin \\\n    + \t\tHEAD:refs/for/next/topic \\\n    +-\t\tHEAD:refs/for/a/b/c/topic \\\n     +\t\tHEAD:refs/review/a/b/c/topic \\\n    -+\t\tHEAD:refs/for/master/topic \\\n    -+\t\t>out 2>&1 &&\n    -+\tmake_user_friendly_and_stable_output <out >actual &&\n    -+\tcat >expect <<-EOF &&\n    -+\tremote: # pre-receive hook\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    + \t\tHEAD:refs/for/master/topic \\\n    + \t\t>out 2>&1 &&\n    + \tmake_user_friendly_and_stable_output <out >actual &&\n    + \tcat >expect <<-EOF &&\n    + \tremote: # pre-receive hook\n    + \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    +-\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: # proc-receive hook\n    -+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    + \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    + \tremote: # proc-receive hook\n    + \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    +-\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n    -+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    + \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    +-\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic ok\n     +\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n    -+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/master/topic ok\n    -+\tremote: # post-receive hook\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    + \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\\0ref=refs/pull/123/head\n    + \tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/master/topic ok\\0ref=refs/pull/124/head forced-update\n    + \tremote: # post-receive hook\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    +-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n    -+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n    -+\tTo <URL/of/upstream.git>\n    -+\t * [new reference] HEAD -> refs/for/next/topic\n    + \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n    + \tTo <URL/of/upstream.git>\n    + \t * [new reference] HEAD -> refs/pull/123/head\n    +-\t * [new reference] HEAD -> refs/for/a/b/c/topic\n     +\t * [new reference] HEAD -> refs/review/a/b/c/topic\n    -+\t * [new reference] HEAD -> refs/for/master/topic\n    -+\tEOF\n    -+\ttest_cmp expect actual &&\n    -+\tgit -C \"$upstream\" show-ref >out &&\n    -+\tmake_user_friendly_and_stable_output <out >actual &&\n    -+\tcat >expect <<-EOF &&\n    -+\t<COMMIT-A> refs/heads/master\n    -+\tEOF\n    -+\ttest_cmp expect actual\n    + \t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n    + \tEOF\n    + \ttest_cmp expect actual &&\n    +@@ t/t5411/common-test-cases.sh: test_expect_success \"proc-receive: report update of mixed refs\" '\n    + \tEOF\n    + \ttest_cmp expect actual\n    + '\n    ++\n    ++test_expect_success \"config add receive.procReceiveRefs = refs\" '\n    ++\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs\n     +'\n     +\n     +test_expect_success \"setup proc-receive hook\" '\n    -+\tcat >\"$upstream/hooks/proc-receive\" <<-EOF &&\n    -+\t#!/bin/sh\n    -+\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    -+\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n    -+\t\t-r \"$A $B refs/for/master/topic ok\"\n    ++\t\t-r \"$B $A refs/heads/master ft\" \\\n    ++\t\t-r \"$A $ZERO_OID refs/heads/foo ft\" \\\n    ++\t\t-r \"$A $B refs/heads/bar ft\" \\\n    ++\t\t-r \"$A $B refs/for/master/topic ok\\0ref=refs/pull/123/head\" \\\n    ++\t\t-r \"$B $A refs/for/next/topic ok\\0ref=refs/pull/124/head forced-update\"\n     +\tEOF\n    -+\tchmod a+x \"$upstream/hooks/proc-receive\"\n     +'\n     +\n    -+# Refs of upstream : master(A)\n    ++# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n     +# Refs of workbench: master(A)  tags/v123\n    -+# git push         :                       bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n    -+test_expect_success \"report mixed refs update\" '\n    -+\tgit -C workbench push origin \\\n    -+\t\tHEAD:refs/heads/bar \\\n    -+\t\tHEAD:refs/heads/baz \\\n    -+\t\tHEAD:refs/for/next/topic \\\n    -+\t\tHEAD:refs/heads/foo \\\n    ++# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n    ++test_expect_success \"proc-receive: process all refs\" '\n    ++\tgit -C workbench push -f origin \\\n    ++\t\tHEAD:refs/heads/master \\\n    ++\t\t:refs/heads/foo \\\n    ++\t\t$B:refs/heads/bar \\\n     +\t\tHEAD:refs/for/master/topic \\\n    ++\t\tHEAD:refs/for/next/topic \\\n     +\t\t>out 2>&1 &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: # pre-receive hook\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n    ++\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n    ++\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n    ++\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n     +\tremote: # proc-receive hook\n    -+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n    ++\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n    ++\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n    -+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/for/master/topic ok\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/heads/master ft\n    ++\tremote: proc-receive> <COMMIT-A> <ZERO-OID> refs/heads/foo ft\n    ++\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/heads/bar ft\n    ++\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/for/master/topic ok\\0ref=refs/pull/123/head\n    ++\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/next/topic ok\\0ref=refs/pull/124/head forced-update\n     +\tremote: # post-receive hook\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n    ++\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n    ++\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n    ++\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n     +\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n    ++\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/next/topic\n     +\tTo <URL/of/upstream.git>\n    -+\t * [new branch] HEAD -> bar\n    -+\t * [new branch] HEAD -> baz\n    -+\t * [new reference] HEAD -> refs/for/next/topic\n    -+\t * [new branch] HEAD -> foo\n    -+\t * [new reference] HEAD -> refs/for/master/topic\n    ++\t <OID-A>..<OID-B> <COMMIT-B> -> bar\n    ++\t - [deleted] foo\n    ++\t + <OID-B>...<OID-A> HEAD -> master (forced update)\n    ++\t <OID-A>..<OID-B> HEAD -> refs/pull/123/head\n    ++\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n     +\tEOF\n     +\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    -+\t<COMMIT-A> refs/heads/bar\n    ++\t<COMMIT-B> refs/heads/bar\n     +\t<COMMIT-A> refs/heads/baz\n    -+\t<COMMIT-A> refs/heads/foo\n     +\t<COMMIT-A> refs/heads/master\n     +\tEOF\n     +\ttest_cmp expect actual\n6:  860860536e < -:  ---------- receive-pack: extension for server-side report\n8:  41159c2ef2 = 7:  2d04f495a3 doc: add documentation for the proc-receive hook\n\n---\n\nJiang Xin (7):\n  transport: not report a non-head push as a branch\n  connect: export parse_feature_value()\n  receive-pack: add new proc-receive hook\n  send-pack: extension for client-side status report\n  refs.c: refactor to reuse ref_is_hidden()\n  receive-pack: new config receive.procReceiveRefs\n  doc: add documentation for the proc-receive hook\n\n Documentation/config/receive.txt           |  14 +\n Documentation/githooks.txt                 |  58 ++\n Makefile                                   |   1 +\n builtin/receive-pack.c                     | 328 ++++++++-\n connect.c                                  |   3 +-\n connect.h                                  |   1 +\n refs.c                                     |  13 +-\n refs.h                                     |   1 +\n remote.c                                   |  28 +\n remote.h                                   |   3 +\n send-pack.c                                |  13 +-\n t/helper/test-proc-receive.c               | 178 +++++\n t/helper/test-tool.c                       |   1 +\n t/helper/test-tool.h                       |   1 +\n t/t5411-proc-receive-hook.sh               |  67 ++\n t/t5411/common-functions.sh                |  54 ++\n t/t5411/common-test-cases.sh               | 794 +++++++++++++++++++++\n t/t5412-proc-receive-hook-http-protocol.sh |  78 ++\n t/t5516-fetch-push.sh                      |   2 +-\n transport-helper.c                         |  66 +-\n transport.c                                |  42 +-\n 21 files changed, 1691 insertions(+), 55 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/common-test-cases.sh\n create mode 100755 t/t5412-proc-receive-hook-http-protocol.sh\n\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395354","messageId":"20200413164828.23260-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200412133022.17590-1-worldhello.net@gmail.com","subject":"[PATCH v11 1/7] transport: not report a non-head push as a branch","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-13T16:48:21Z","receivedAt":"2020-04-13T16:48:41Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing a new reference (not a head or tag), report it as a new\nreference instead of a new branch.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411-proc-receive-hook.sh               | 67 +++++++++++++++++++\n t/t5411/common-functions.sh                | 50 ++++++++++++++\n t/t5411/common-test-cases.sh               | 43 ++++++++++++\n t/t5412-proc-receive-hook-http-protocol.sh | 78 ++++++++++++++++++++++\n t/t5516-fetch-push.sh                      |  2 +-\n transport.c                                |  9 ++-\n 6 files changed, 245 insertions(+), 4 deletions(-)\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/common-test-cases.sh\n create mode 100755 t/t5412-proc-receive-hook-http-protocol.sh\n\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nnew file mode 100755\nindex 0000000000..3f4bee76d9\n--- /dev/null\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -0,0 +1,67 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook'\n+\n+. ./test-lib.sh\n+\n+. \"$TEST_DIRECTORY\"/t5411/common-functions.sh\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  In addition to the common format method,\n+# we also replace the URL of different protocol for the upstream repository\n+# with a fixed pattern.\n+make_user_friendly_and_stable_output () {\n+\tmake_user_friendly_and_stable_output_common |\n+\t\tsed -e \"s#To ../upstream.git#To <URL/of/upstream.git>#\"\n+}\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+test_expect_success \"setup\" '\n+\tgit init --bare upstream.git &&\n+\tgit init workbench &&\n+\tcreate_commits_in workbench A B &&\n+\t(\n+\t\tcd workbench &&\n+\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\tgit config core.abbrev 7 &&\n+\t\tgit remote add origin ../upstream.git &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit tag -m \"v123\" v123 $A &&\n+\t\tgit push origin \\\n+\t\t\t$B:refs/heads/master \\\n+\t\t\t$A:refs/heads/next\n+\t) &&\n+\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t# setup pre-receive hook\n+\twrite_script upstream.git/hooks/pre-receive <<-\\EOF &&\n+\texec >&2\n+\techo \"# pre-receive hook\"\n+\twhile read old new ref\n+\tdo\n+\t\techo \"pre-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\t# setup post-receive hook\n+\twrite_script upstream.git/hooks/post-receive <<-\\EOF &&\n+\texec >&2\n+\techo \"# post-receive hook\"\n+\twhile read old new ref\n+\tdo\n+\t\techo \"post-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\tupstream=upstream.git\n+'\n+\n+# Include test cases for both file and HTTP protocol\n+. \"$TEST_DIRECTORY\"/t5411/common-test-cases.sh\n+\n+test_done\ndiff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\nnew file mode 100644\nindex 0000000000..4337b10c8c\n--- /dev/null\n+++ b/t/t5411/common-functions.sh\n@@ -0,0 +1,50 @@\n+# Create commits in <repo> and assign each commit's oid to shell variables\n+# given in the arguments (A, B, and C). E.g.:\n+#\n+#     create_commits_in <repo> A B C\n+#\n+# NOTE: Never calling this function from a subshell since variable\n+# assignments will disappear when subshell exits.\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{} --)\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  We can easily prepare the expect text\n+# without having to worry about future changes of the commit ID and spaces\n+# of the output.  We also replce single quotes with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect text.\n+make_user_friendly_and_stable_output_common () {\n+\tsed \\\n+\t\t-e \"s/  *\\$//\" \\\n+\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/'/\\\"/g\" \\\n+\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n+\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n+\t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n+\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n+\t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n+\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\"\n+}\ndiff --git a/t/t5411/common-test-cases.sh b/t/t5411/common-test-cases.sh\nnew file mode 100644\nindex 0000000000..23655846e4\n--- /dev/null\n+++ b/t/t5411/common-test-cases.sh\n@@ -0,0 +1,43 @@\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"normal git-push command\" '\n+\tgit -C workbench push -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tHEAD:refs/heads/master \\\n+\t\tHEAD:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo <URL/of/upstream.git>\n+\t + <OID-B>...<OID-A> HEAD -> master (forced update)\n+\t - [deleted] next\n+\t * [new tag] v123 -> v123\n+\t * [new reference] HEAD -> refs/review/master/topic\n+\t * [new branch] HEAD -> a/b/c\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5412-proc-receive-hook-http-protocol.sh b/t/t5412-proc-receive-hook-http-protocol.sh\nnew file mode 100755\nindex 0000000000..d102bc438b\n--- /dev/null\n+++ b/t/t5412-proc-receive-hook-http-protocol.sh\n@@ -0,0 +1,78 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook for HTTP protocol'\n+\n+. ./test-lib.sh\n+\n+ROOT_PATH=\"$PWD\"\n+. \"$TEST_DIRECTORY\"/lib-gpg.sh\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+. \"$TEST_DIRECTORY\"/lib-terminal.sh\n+start_httpd\n+\n+. \"$TEST_DIRECTORY\"/t5411/common-functions.sh\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  In addition to the common format method,\n+# we also replace the URL of different protocol for the upstream repository\n+# with a fixed pattern.\n+make_user_friendly_and_stable_output () {\n+\tmake_user_friendly_and_stable_output_common |\n+\t\tsed -e \"s#To http:.*/upstream.git#To <URL/of/upstream.git>#\"\n+}\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+test_expect_success \"setup\" '\n+\tgit init --bare upstream.git &&\n+\tgit -C upstream.git config http.receivepack true &&\n+\tgit init workbench &&\n+\tcreate_commits_in workbench A B &&\n+\t(\n+\t\tcd workbench &&\n+\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\tgit config core.abbrev 7 &&\n+\t\tgit remote add origin ../upstream.git &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit tag -m \"v123\" v123 $A &&\n+\t\tgit push origin \\\n+\t\t\t$B:refs/heads/master \\\n+\t\t\t$A:refs/heads/next\n+\t) &&\n+\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t# setup pre-receive hook\n+\twrite_script upstream.git/hooks/pre-receive <<-\\EOF &&\n+\texec >&2\n+\techo \"# pre-receive hook\"\n+\twhile read old new ref\n+\tdo\n+\t\techo \"pre-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\t# setup post-receive hook\n+\twrite_script upstream.git/hooks/post-receive <<-\\EOF &&\n+\texec >&2\n+\techo \"# post-receive hook\"\n+\twhile read old new ref\n+\tdo\n+\t\techo \"post-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\tupstream=\"$HTTPD_DOCUMENT_ROOT_PATH/upstream.git\" &&\n+\tmv upstream.git \"$upstream\" &&\n+\tgit -C workbench remote set-url origin $HTTPD_URL/smart/upstream.git\n+'\n+\n+setup_askpass_helper\n+\n+# Include test cases for both file and HTTP protocol\n+. \"$TEST_DIRECTORY\"/t5411/common-test-cases.sh\n+\n+test_done\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 9ff041a093..9e4b9313b5 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1039,7 +1039,7 @@ test_force_fetch_tag \"annotated tag\" \"-f -a -m'tag message'\"\n test_expect_success 'push --porcelain' '\n \tmk_empty testrepo &&\n \techo >.git/foo  \"To testrepo\" &&\n-\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new branch]\"  &&\n+\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new reference]\"  &&\n \techo >>.git/foo \"Done\" &&\n \tgit push >.git/bar --porcelain  testrepo refs/heads/master:refs/remotes/origin/master &&\n \t(\ndiff --git a/transport.c b/transport.c\nindex 1fdc7dac1a..272c0f4046 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -500,9 +500,12 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\t\t\t porcelain, summary_width);\n \telse if (is_null_oid(&ref->old_oid))\n \t\tprint_ref_status('*',\n-\t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n-\t\t\t\"[new branch]\"),\n-\t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t  ? \"[new tag]\"\n+\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t     ? \"[new branch]\"\n+\t\t\t\t     : \"[new reference]\")),\n+\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395356","messageId":"20200413164828.23260-4-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200412133022.17590-1-worldhello.net@gmail.com","subject":"[PATCH v11 3/7] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-13T16:48:23Z","receivedAt":"2020-04-13T16:48:45Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nGit calls an internal `execute_commands` function to handle commands\nsent from client to `git-receive-pack`.  Regardless of what references\nthe user pushes, git creates or updates the corresponding references if\nthe user has write-permission.  A contributor who has no\nwrite-permission, cannot push to the repository directly.  So, the\ncontributor has to write commits to an alternate location, and sends\npull request by emails or by other ways.  We call this workflow as a\ndistributed workflow.\n\nIt would be more convenient to work in a centralized workflow like what\nGerrit provided for some cases.  For example, a read-only user who\ncannot push to a branch directly can run the following `git push`\ncommand to push commits to a pseudo reference (has a prefix \"refs/for/\",\nnot \"refs/heads/\") to create a code review.\n\n    git push origin \\\n        HEAD:refs/for/<branch-name>/<session>\n\nThe `<branch-name>` in the above example can be as simple as \"master\",\nor a more complicated branch name like \"foo/bar\".  The `<session>` in\nthe above example command can be the local branch name of the client\nside, such as \"my/topic\".\n\nWe cannot implement a centralized workflow elegantly by using\n\"pre-receive\" + \"post-receive\", because Git will call the internal\nfunction \"execute_commands\" to create references (even the special\npseudo reference) between these two hooks.  Even though we can delete\nthe temporarily created pseudo reference via the \"post-receive\" hook,\nhaving a temporary reference is not safe for concurrent pushes.\n\nSo, add a filter and a new handler to support this kind of workflow.\nThe filter will check the prefix of the reference name, and if the\ncommand has a special reference name, the filter will turn a specific\nfield (`run_proc_receive`) on for the command.  Commands with this filed\nturned on will be executed by a new handler (an hook named\n\"proc-receive\") instead of the internal `execute_commands` function.\nWe can use this \"proc-receive\" command to create pull requests or send\nemails for code review.\n\nSuggested by Junio, this \"proc-receive\" hook reads the commands,\npush-options (optional), and send result using a protocol in pkt-line\nformat.  In the following example, The letter \"S\" stands for\n\"receive-pack\" and letter \"H\" stands for the hook.\n\n    # Version and features negotiation.\n    S: PKT-LINE(version=1\\0push-options atomic...)\n    S: flush-pkt\n    H: PKT-LINE(version=1\\0push-options...)\n    H: flush-pkt\n\n    # Send commands from server to the hook.\n    S: PKT-LINE(<old-oid> <new-oid> <ref>)\n    S: ... ...\n    S: flush-pkt\n    # Send push-options only if the 'push-options' feature is enabled.\n    S: PKT-LINE(push-option)\n    S: ... ...\n    S: flush-pkt\n\n    # Receive result from the hook.\n    # OK, run this command successfully.\n    H: PKT-LINE(<old-oid> <new-oid> <ref> ok)\n    # NO, I reject it.\n    H: PKT-LINE(<old-oid> <new-oid> <ref> ng <reason>)\n    # Fall through, let 'receive-pack' to execute it.\n    H: PKT-LINE(<old-oid> <new-oid> <ref> ft)\n    # OK, but has an alternate reference.  The alternate reference name\n    # and other status are given in key=value pairs after the null\n    # character.\n    H: PKT-LINE(<old-oid> <new-oid> <ref> ok\\0ref=refs/pull/123/head\n                forced-update)\n    H: ... ...\n    H: flush-pkt\n\nAfter receiving a command, the hook will execute the command, and may\ncreate/update different reference.  For example, a command for a pseudo\nreference \"refs/for/master/topic\" may create/update different reference\nsuch as \"refs/pull/123/head\".  The alternate reference name and other\nstatus are given in key-value pairs as extended status of the report\nline.\n\nThe list of commands returned from \"proc-receive\" will replace the\nrelevant commands that are sent from user to \"receive-pack\", and\n\"receive-pack\" will continue to run the \"execute_commands\" function and\nother routines.  Finally, the result of the execution of these commands\nwill be reported to end user.\n\nThe reporting function from \"receive-pack\" to \"send-pack\" is also\nextended using a backward compatible way by adding key-value pairs\nafter an null character, like:\n\n    # OK, run this command successfully with optional extended-status.\n    ok <reference>\\0ref=refs/pull/123/head old-oid=...\n\n    # NO, I reject it.\n    ng <reference> <error message>\n\nParsing the extended status of the report lines for \"send-pack\" is not\nimplemented in this commit, so we can make a compatible test on old\nversion of a Git client.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Makefile                     |   1 +\n builtin/receive-pack.c       | 299 +++++++++++++++-\n t/helper/test-proc-receive.c | 178 +++++++++\n t/helper/test-tool.c         |   1 +\n t/helper/test-tool.h         |   1 +\n t/t5411/common-functions.sh  |  10 +-\n t/t5411/common-test-cases.sh | 673 +++++++++++++++++++++++++++++++++++\n 7 files changed, 1151 insertions(+), 12 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n\ndiff --git a/Makefile b/Makefile\nindex ef1ff2228f..1c52c280ce 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -725,6 +725,7 @@ TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\n+TEST_BUILTINS_OBJS += test-proc-receive.o\n TEST_BUILTINS_OBJS += test-progress.o\n TEST_BUILTINS_OBJS += test-reach.o\n TEST_BUILTINS_OBJS += test-read-cache.o\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 2cc18bbffd..fd2dc23c2e 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -308,11 +308,15 @@ static void write_head_info(void)\n \tpacket_flush(1);\n }\n \n+#define RUN_PROC_RECEIVE_SCHEDULED\t1\n+#define RUN_PROC_RECEIVE_RETURNED\t2\n struct command {\n \tstruct command *next;\n \tconst char *error_string;\n+\tconst char *extended_status;\n \tunsigned int skip_update:1,\n-\t\t     did_not_exist:1;\n+\t\t     did_not_exist:1,\n+\t\t     run_proc_receive:2;\n \tint index;\n \tstruct object_id old_oid;\n \tstruct object_id new_oid;\n@@ -817,6 +821,250 @@ static int run_update_hook(struct command *cmd)\n \treturn finish_command(&proc);\n }\n \n+static struct command *find_command_by_refname(const struct command *list,\n+\t\t\t\t\t       const char *refname)\n+{\n+\tfor (; list; list = list->next)\n+\t\tif (!strcmp(list->ref_name, refname))\n+\t\t\treturn (struct command *)list;\n+\treturn NULL;\n+}\n+\n+static int read_proc_receive_report(struct packet_reader *reader,\n+\t\t\t\t    struct command *commands,\n+\t\t\t\t    struct strbuf *errmsg)\n+{\n+\tstruct command *cmd;\n+\tstruct command *hint = NULL;\n+\tstruct strbuf extended_status = STRBUF_INIT;\n+\tint code = 0;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\t\tchar *status;\n+\t\tchar *msg = NULL;\n+\t\tint len;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\t\tlen = strlen(reader->line);\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ') {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\\n\",\n+\t\t\t\t    reader->line);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\trefname = p;\n+\t\tstatus = strchr(p, ' ');\n+\t\tif (!status) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\\n\",\n+\t\t\t\t    reader->line);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\t*status++ = '\\0';\n+\t\tmsg = strchr(status, ' ');\n+\t\tif (msg)\n+\t\t\t*msg++ = '\\0';\n+\n+\t\t/* first try searching at our hint, falling back to all refs */\n+\t\tif (hint)\n+\t\t\thint = find_command_by_refname(hint, refname);\n+\t\tif (!hint)\n+\t\t\thint = find_command_by_refname(commands, refname);\n+\t\tif (!hint) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unknown ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!hint->run_proc_receive) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unexpected ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(status, \"ng\")) {\n+\t\t\tif (msg)\n+\t\t\t\thint->error_string = xstrdup(msg);\n+\t\t\telse\n+\t\t\t\thint->error_string = \"failed\";\n+\t\t\tcode = -1;\n+\t\t} else if (!strcmp(\"ft\", status)) {\n+\t\t\t/* Reset \"run_proc_receive\" field, and continue to run in \"receive-pack\" */\n+\t\t\thint->run_proc_receive = 0;\n+\t\t} else if (strcmp(\"ok\", status)) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported bad status '%s' on ref '%s'\\n\",\n+\t\t\t\t    status, refname);\n+\t\t\tcode = -1;\n+\t\t\t/* Skip marking it as RUN_PROC_RECEIVE_RETURNED */\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (reader->pktlen > len)\n+\t\t\tstrbuf_addstr(&extended_status, (char *)reader->line + len + 1);\n+\t\tif (oidcmp(&hint->old_oid, &old_oid)) {\n+\t\t\toidcpy(&hint->old_oid, &old_oid);\n+\t\t\tstrbuf_addf(&extended_status, \"%sold-oid=%s\",\n+\t\t\t\t    extended_status.len > 0 ? \" \": \"\",\n+\t\t\t\t    oid_to_hex(&old_oid));\n+\t\t}\n+\t\tif (oidcmp(&hint->new_oid, &new_oid)) {\n+\t\t\toidcpy(&hint->new_oid, &new_oid);\n+\t\t\tstrbuf_addf(&extended_status, \"%snew-oid=%s\",\n+\t\t\t\t    extended_status.len > 0 ? \" \": \"\",\n+\t\t\t\t    oid_to_hex(&new_oid));\n+\t\t}\n+\t\tif (extended_status.len > 0)\n+\t\t\thint->extended_status = strbuf_detach(&extended_status, NULL);\n+\t\tif (hint->run_proc_receive)\n+\t\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n+\t}\n+\n+\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\tif (cmd->run_proc_receive && !cmd->error_string &&\n+\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {\n+\t\t    cmd->error_string = \"proc-receive failed to report status\";\n+\t\t    code = -1;\n+\t\t}\n+\n+\treturn code;\n+}\n+\n+static int run_proc_receive_hook(struct command *commands,\n+\t\t\t\t const struct string_list *push_options)\n+{\n+\tstruct child_process proc = CHILD_PROCESS_INIT;\n+\tstruct async muxer;\n+\tstruct command *cmd;\n+\tconst char *argv[2];\n+\tstruct packet_reader reader;\n+\tstruct strbuf cap = STRBUF_INIT;\n+\tstruct strbuf errmsg = STRBUF_INIT;\n+\tint pr_use_push_options = 0;\n+\tint version = 0;\n+\tint code;\n+\n+\targv[0] = find_hook(\"proc-receive\");\n+\tif (!argv[0]) {\n+\t\trp_error(\"cannot find hook 'proc-receive'\");\n+\t\treturn -1;\n+\t}\n+\targv[1] = NULL;\n+\n+\tproc.argv = argv;\n+\tproc.in = -1;\n+\tproc.out = -1;\n+\tproc.trace2_hook_name = \"proc-receive\";\n+\n+\tif (use_sideband) {\n+\t\tmemset(&muxer, 0, sizeof(muxer));\n+\t\tmuxer.proc = copy_to_sideband;\n+\t\tmuxer.in = -1;\n+\t\tcode = start_async(&muxer);\n+\t\tif (code)\n+\t\t\treturn code;\n+\t\tproc.err = muxer.in;\n+\t} else {\n+\t\tproc.err = 0;\n+\t}\n+\n+\tcode = start_command(&proc);\n+\tif (code) {\n+\t\tif (use_sideband)\n+\t\t\tfinish_async(&muxer);\n+\t\treturn code;\n+\t}\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\n+\t/* Version negotiaton */\n+\tpacket_reader_init(&reader, proc.out, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\tif (use_atomic)\n+\t\tstrbuf_addstr(&cap, \" atomic\");\n+\tif (use_push_options)\n+\t\tstrbuf_addstr(&cap, \" push-options\");\n+\tif (cap.len) {\n+\t\tpacket_write_fmt(proc.in, \"version=1%c%s\\n\", '\\0', cap.buf + 1);\n+\t\tstrbuf_release(&cap);\n+\t} else {\n+\t\tpacket_write_fmt(proc.in, \"version=1\\n\");\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader.pktlen > 8 && starts_with(reader.line, \"version=\")) {\n+\t\t\tversion = atoi(reader.line + 8);\n+\t\t\tlinelen = strlen(reader.line);\n+\t\t\tif (linelen < reader.pktlen) {\n+\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tpr_use_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (version != 1) {\n+\t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n+\t\t\t    version);\n+\t\tcode = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\t/* Send commands */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)\n+\t\t\tcontinue;\n+\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n+\t\t\t\t oid_to_hex(&cmd->old_oid),\n+\t\t\t\t oid_to_hex(&cmd->new_oid),\n+\t\t\t\t cmd->ref_name);\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\t/* Send push options */\n+\tif (pr_use_push_options) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tfor_each_string_list_item(item, push_options)\n+\t\t\tpacket_write_fmt(proc.in, \"%s\", item->string);\n+\t\tpacket_flush(proc.in);\n+\t}\n+\n+\t/* Read result from proc-receive */\n+\tcode = read_proc_receive_report(&reader, commands, &errmsg);\n+\n+cleanup:\n+\tclose(proc.in);\n+\tclose(proc.out);\n+\tif (use_sideband)\n+\t\tfinish_async(&muxer);\n+\tif (finish_command(&proc))\n+\t\tcode = -1;\n+\tif (errmsg.len >0) {\n+\t\tchar *p = errmsg.buf;\n+\n+\t\tp += errmsg.len - 1;\n+\t\tif (*p == '\\n')\n+\t\t\t*p = '\\0';\n+\t\trp_error(\"%s\", errmsg.buf);\n+\t\tstrbuf_release(&errmsg);\n+\t}\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn code;\n+}\n+\n static char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n@@ -1392,7 +1640,7 @@ static void execute_commands_non_atomic(struct command *commands,\n \tstruct strbuf err = STRBUF_INIT;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\ttransaction = ref_transaction_begin(&err);\n@@ -1432,7 +1680,7 @@ static void execute_commands_atomic(struct command *commands,\n \t}\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\tcmd->error_string = update(cmd, si);\n@@ -1468,6 +1716,7 @@ static void execute_commands(struct command *commands,\n \tstruct iterate_data data;\n \tstruct async muxer;\n \tint err_fd = 0;\n+\tint run_proc_receive = 0;\n \n \tif (unpacker_error) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n@@ -1497,6 +1746,21 @@ static void execute_commands(struct command *commands,\n \n \treject_updates_to_hidden(commands);\n \n+\t/*\n+\t * Try to find commands that have special prefix in their reference names,\n+\t * and mark them to run an external \"proc-receive\" hook later.\n+\t */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!should_process_cmd(cmd))\n+\t\t\tcontinue;\n+\n+\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n+\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n+\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\trun_proc_receive = 1;\n+\t\t}\n+\t}\n+\n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n \t\t\tif (!cmd->error_string)\n@@ -1523,6 +1787,12 @@ static void execute_commands(struct command *commands,\n \tfree(head_name_to_free);\n \thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, NULL, NULL);\n \n+\tif (run_proc_receive &&\n+\t    run_proc_receive_hook(commands, push_options))\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tif (!cmd->error_string  && (cmd->run_proc_receive || use_atomic))\n+\t\t\t\tcmd->error_string = \"fail to run proc-receive hook\";\n+\n \tif (use_atomic)\n \t\texecute_commands_atomic(commands, si);\n \telse\n@@ -1899,12 +2169,23 @@ static void report(struct command *commands, const char *unpack_status)\n \tpacket_buf_write(&buf, \"unpack %s\\n\",\n \t\t\t unpack_status ? unpack_status : \"ok\");\n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!cmd->error_string)\n-\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n-\t\t\t\t\t cmd->ref_name);\n-\t\telse\n-\t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n-\t\t\t\t\t cmd->ref_name, cmd->error_string);\n+\t\tif (!cmd->error_string) {\n+\t\t\tif (cmd->extended_status)\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s%c%s\\n\",\n+\t\t\t\t\t\t cmd->ref_name, '\\0',\n+\t\t\t\t\t\t cmd->extended_status);\n+\t\t\telse\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n+\t\t\t\t\t\t cmd->ref_name);\n+\t\t} else {\n+\t\t\tif (cmd->extended_status)\n+\t\t\t\tpacket_buf_write(&buf, \"ng %s %s%c%s\\n\",\n+\t\t\t\t\t\t cmd->ref_name, cmd->error_string,\n+\t\t\t\t\t\t '\\0', cmd->extended_status);\n+\t\t\telse\n+\t\t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n+\t\t\t\t\t\t cmd->ref_name, cmd->error_string);\n+\t\t}\n \t}\n \tpacket_buf_flush(&buf);\n \ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nnew file mode 100644\nindex 0000000000..c9f5c5aae4\n--- /dev/null\n+++ b/t/helper/test-proc-receive.c\n@@ -0,0 +1,178 @@\n+#include \"cache.h\"\n+#include \"connect.h\"\n+#include \"parse-options.h\"\n+#include \"pkt-line.h\"\n+#include \"sigchain.h\"\n+#include \"string-list.h\"\n+#include \"test-tool.h\"\n+\n+static const char *proc_receive_usage[] = {\n+\t\"test-tool proc-receive [<options>...]\",\n+\tNULL\n+};\n+\n+static int version = 1;\n+static int verbose = 0;\n+static int no_push_options = 0;\n+static int use_atomic = 0;\n+static int use_push_options = 0;\n+static struct string_list returns = STRING_LIST_INIT_NODUP;\n+\n+struct command {\n+\tstruct command *next;\n+\tconst char *error_string;\n+\tunsigned int skip_update:1,\n+\t\t     did_not_exist:1;\n+\tint index;\n+\tstruct object_id old_oid;\n+\tstruct object_id new_oid;\n+\tchar ref_name[FLEX_ARRAY]; /* more */\n+};\n+\n+static void proc_receive_verison(struct packet_reader *reader) {\n+\tint server_version = 0;\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader->pktlen > 8 && starts_with(reader->line, \"version=\")) {\n+\t\t\tserver_version = atoi(reader->line+8);\n+\t\t\tlinelen = strlen(reader->line);\n+\t\t\tif (linelen < reader->pktlen) {\n+\t\t\t\tconst char *feature_list = reader->line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"atomic\"))\n+\t\t\t\t\tuse_atomic= 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tuse_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (server_version != 1)\n+\t\tdie(\"bad protocol version: %d\", server_version);\n+\n+\tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n+\t\t\t version, '\\0',\n+\t\t\t use_push_options && !no_push_options ? \"push-options\": \"\");\n+\tpacket_flush(1);\n+}\n+\n+static void proc_receive_read_commands(struct packet_reader *reader,\n+\t\t\t\t       struct command **commands)\n+{\n+\tstruct command **tail = commands;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tstruct command *cmd;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ')\n+\t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n+\t\t\t    reader->line);\n+\t\trefname = p;\n+\t\tFLEX_ALLOC_STR(cmd, ref_name, refname);\n+\t\toidcpy(&cmd->old_oid, &old_oid);\n+\t\toidcpy(&cmd->new_oid, &new_oid);\n+\n+\t\t*tail = cmd;\n+\t\ttail = &cmd->next;\n+\t}\n+}\n+\n+static void proc_receive_read_push_options(struct packet_reader *reader,\n+\t\t\t\t\t   struct string_list *options)\n+{\n+\n+\tif (no_push_options || !use_push_options)\n+\t       return;\n+\n+\twhile (1) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tstring_list_append(options, reader->line);\n+\t}\n+}\n+\n+int cmd__proc_receive(int argc, const char **argv)\n+{\n+\tstruct packet_reader reader;\n+\tstruct command *commands = NULL;\n+\tstruct string_list push_options = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"no-push-options\", &no_push_options,\n+\t\t\t \"disable push options\"),\n+\t\tOPT_STRING_LIST('r', \"return\", &returns, \"old/new/ref/status/msg\",\n+\t\t\t\t\"return of results\"),\n+\t\tOPT__VERBOSE(&verbose, \"be verbose\"),\n+\t\tOPT_INTEGER('V', \"version\", &version,\n+\t\t\t    \"use this protocol version number\"),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, \"test-tools\", options, proc_receive_usage, 0);\n+\tif (argc > 0)\n+\t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n+\n+\tpacket_reader_init(&reader, 0, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\tproc_receive_verison(&reader);\n+\tproc_receive_read_commands(&reader, &commands);\n+\tproc_receive_read_push_options(&reader, &push_options);\n+\n+\tif (verbose) {\n+\t\tstruct command *cmd;\n+\n+\t\tif (use_push_options || use_atomic)\n+\t\t\tfprintf(stderr, \"proc-receive:%s%s\\n\",\n+\t\t\t\tuse_atomic? \" atomic\": \"\",\n+\t\t\t\tuse_push_options ? \" push_options\": \"\");\n+\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tfprintf(stderr, \"proc-receive< %s %s %s\\n\",\n+\t\t\t\toid_to_hex(&cmd->old_oid),\n+\t\t\t\toid_to_hex(&cmd->new_oid),\n+\t\t\t\tcmd->ref_name);\n+\n+\t\tif (push_options.nr > 0)\n+\t\t\tfor_each_string_list_item(item, &push_options)\n+\t\t\t\tfprintf(stderr, \"proc-receive< %s\\n\", item->string);\n+\n+\t\tif (returns.nr)\n+\t\t\tfor_each_string_list_item(item, &returns)\n+\t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n+\t}\n+\n+\tif (returns.nr)\n+\t\tfor_each_string_list_item(item, &returns) {\n+\t\t\tchar *p;\n+\n+\t\t\tp = strstr(item->string, \"\\\\0\");\n+\t\t\tif (p) {\n+\t\t\t\t*p = '\\0';\n+\t\t\t\tp += 2;\n+\t\t\t\tpacket_write_fmt(1, \"%s%c%s\\n\", item->string, '\\0', p);\n+\t\t\t} else {\n+\t\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n+\t\t\t}\n+\t\t}\n+\tpacket_flush(1);\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 31eedcd241..f865433a61 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -44,6 +44,7 @@ static struct test_cmd cmds[] = {\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\n+\t{ \"proc-receive\", cmd__proc_receive},\n \t{ \"progress\", cmd__progress },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 4eb5e6609e..059008cf22 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -34,6 +34,7 @@ int cmd__parse_pathspec_file(int argc, const char** argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\n+int cmd__proc_receive(int argc, const char **argv);\n int cmd__progress(int argc, const char **argv);\n int cmd__reach(int argc, const char **argv);\n int cmd__read_cache(int argc, const char **argv);\ndiff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\nindex 4337b10c8c..1fbd24dbaf 100644\n--- a/t/t5411/common-functions.sh\n+++ b/t/t5411/common-functions.sh\n@@ -34,8 +34,11 @@ create_commits_in () {\n # Format the output of git-push, git-show-ref and other commands to make a\n # user-friendly and stable text.  We can easily prepare the expect text\n # without having to worry about future changes of the commit ID and spaces\n-# of the output.  We also replce single quotes with double quotes, because\n-# it is boring to prepare unquoted single quotes in expect text.\n+# of the output.  Single quotes are replaced with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect text.  We also\n+# remove some locale error messages, which break test if we turn on\n+# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n+# on plumbing commands.\n make_user_friendly_and_stable_output_common () {\n \tsed \\\n \t\t-e \"s/  *\\$//\" \\\n@@ -46,5 +49,6 @@ make_user_friendly_and_stable_output_common () {\n \t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n \t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n \t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n-\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\"\n+\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\" \\\n+\t\t-e \"/^error: / d\"\n }\ndiff --git a/t/t5411/common-test-cases.sh b/t/t5411/common-test-cases.sh\nindex 23655846e4..fe970df755 100644\n--- a/t/t5411/common-test-cases.sh\n+++ b/t/t5411/common-test-cases.sh\n@@ -41,3 +41,676 @@ test_expect_success \"normal git-push command\" '\n \tEOF\n \ttest_cmp expect actual\n '\n+\n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, fail to push special ref\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, fail all for atomic push\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\t$B:master \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] <COMMIT-B> -> master (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown version)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (unknown version)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\t# Check status report for git-push\n+\tsed -n \\\n+\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n+\t\t-e \"/^ / { s/   */ /g; p; }\" \\\n+\t\t<actual >actual-report &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual-report &&\n+\n+\t# Check error message from \"receive-pack\", but ignore unstable fatal error\n+\t# message (\"remote: fatal: the remote end hung up unexpectedly\") which\n+\t# is different from the remote HTTP server with different locale settings.\n+\tsed -n -e \"/^remote: error:/ { s/   */ /g; p; }\" \\\n+\t\t<actual >actual-error &&\n+\tcat >expect <<-EOF &&\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tEOF\n+\ttest_cmp expect actual-error &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (no report)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (bad oid)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"bad-id new-id ref ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (bad oid)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> bad-id new-id ref ok\n+\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"bad-id new-id ref ok\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no status)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (no status)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic xx msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (unknown status)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic xx msg\n+\tremote: error: proc-receive reported bad status \"xx\" on ref \"refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng message)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng error msg\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unexpected ref)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/heads/master ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: report on unexpected ref\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/heads/master ok\n+\tremote: error: proc-receive reported status on unexpected ref: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\n+\n+test_expect_success \"setup proc-receive hook (ok)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: failed to report status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"proc-receive: not support push options\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n+\t\tactual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"proc-receive: push with options\" '\n+\tgit -C workbench push \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+test_expect_success \"setup proc-receive hook (ft)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $B refs/for/master/topic ft\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(B)\n+test_expect_success \"proc-receive: fall throught, let receive-pack to execute\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-B> refs/for/master/topic ft\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] <COMMIT-B> -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/for/master/topic\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             refs/for/master/topic(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/for/master/topic\n+'\n+\n+test_expect_success \"setup proc-receive hook (with extended status)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/a/b/c/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\\0ref=refs/pull/123/head\" \\\n+\t\t-r \"$B $A refs/for/master/topic ok\\0ref=refs/pull/124/head forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report with extended status\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/for/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\\0ref=refs/pull/123/head\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/master/topic ok\\0ref=refs/pull/124/head forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/for/a/b/c/topic\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n+\t\t-r \"$A $B refs/for/master/topic ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report update of mixed refs\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/for/master/topic ok\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t * [new branch] HEAD -> bar\n+\t * [new branch] HEAD -> baz\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new branch] HEAD -> foo\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395355","messageId":"20200413164828.23260-7-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200412133022.17590-1-worldhello.net@gmail.com","subject":"[PATCH v11 6/7] receive-pack: new config receive.procReceiveRefs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-13T16:48:26Z","receivedAt":"2020-04-13T16:48:47Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd a new multi-valued config variable \"receive.procReceiveRefs\"\nfor `receive-pack` command, like the follows:\n\n    git config --system --add receive.procReceiveRefs refs/for\n    git config --system --add receive.procReceiveRefs refs/drafts\n\nIf the specific prefix strings match the reference names of the commands\nwhich are sent from git client to `receive-pack`, these commands will be\nexecuted by an external hook (named \"proc-receive\"), instead of the\ninternal `execute_commands` function.\n\nFor example, if it is set to \"refs/for\", pushing to a reference such as\n\"refs/for/master\" will not create or update reference \"refs/for/master\",\nbut may create or update a pull request directly by running the hook\n\"proc-receive\".\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/config/receive.txt | 14 +++++\n builtin/receive-pack.c           | 43 ++++++++++++---\n t/t5411/common-test-cases.sh     | 94 +++++++++++++++++++++++++++++---\n 3 files changed, 136 insertions(+), 15 deletions(-)\n\ndiff --git a/Documentation/config/receive.txt b/Documentation/config/receive.txt\nindex 65f78aac37..e7b967feeb 100644\n--- a/Documentation/config/receive.txt\n+++ b/Documentation/config/receive.txt\n@@ -114,6 +114,20 @@ receive.hideRefs::\n \tAn attempt to update or delete a hidden ref by `git push` is\n \trejected.\n \n+receive.procReceiveRefs::\n+\tThis is a multi-valued variable that defines reference prefixes\n+\tto match the commands in `receive-pack`.  Commands matching the\n+\tprefixes will be executed by an external hook \"proc-receive\",\n+\tinstead of the internal `execute_commands` function.  If this\n+\tvariable is not defined, the \"proc-receive\" hook will never be\n+\tused, and all commands will be executed by the internal\n+\t`execute_commands` function.\n+\n+\tFor example, if this variable is set to \"refs/for\", pushing to\n+\treference such as \"refs/for/master\" will not create or update a\n+\treference named \"refs/for/master\", but may create or update a\n+\tpull request directly by running the hook \"proc-receive\".\n+\n receive.updateServerInfo::\n \tIf set to true, git-receive-pack will run git-update-server-info\n \tafter receiving data from git-push and updating refs.\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex fd2dc23c2e..7f39f8db9a 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -76,6 +76,7 @@ static struct object_id push_cert_oid;\n static struct signature_check sigcheck;\n static const char *push_cert_nonce;\n static const char *cert_nonce_seed;\n+static struct string_list proc_receive_refs;\n \n static const char *NONCE_UNSOLICITED = \"UNSOLICITED\";\n static const char *NONCE_BAD = \"BAD\";\n@@ -228,6 +229,20 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (strcmp(var, \"receive.procreceiverefs\") == 0) {\n+\t\tchar *prefix;\n+\t\tint len;\n+\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tprefix = xstrdup(value);\n+\t\tlen = strlen(prefix);\n+\t\twhile (len && prefix[len - 1] == '/')\n+\t\t\tprefix[--len] = '\\0';\n+\t\tstring_list_append(&proc_receive_refs, prefix);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -1750,15 +1765,26 @@ static void execute_commands(struct command *commands,\n \t * Try to find commands that have special prefix in their reference names,\n \t * and mark them to run an external \"proc-receive\" hook later.\n \t */\n-\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n-\t\t\tcontinue;\n+\tif (proc_receive_refs.nr > 0) {\n+\t\tstruct strbuf refname_full = STRBUF_INIT;\n+\t\tsize_t prefix_len;\n \n-\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n-\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n-\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n-\t\t\trun_proc_receive = 1;\n+\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n+\t\tprefix_len = refname_full.len;\n+\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tif (!should_process_cmd(cmd))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_setlen(&refname_full, prefix_len);\n+\t\t\tstrbuf_addstr(&refname_full, cmd->ref_name);\n+\t\t\tif (ref_matches(&proc_receive_refs, cmd->ref_name, refname_full.buf)) {\n+\t\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\t\trun_proc_receive = 1;\n+\t\t\t}\n \t\t}\n+\n+\t\tstrbuf_release(&refname_full);\n \t}\n \n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n@@ -2223,6 +2249,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n+\tstring_list_init(&proc_receive_refs, 0);\n+\n \tpacket_trace_identity(\"receive-pack\");\n \n \targc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);\n@@ -2338,5 +2366,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \toid_array_clear(&shallow);\n \toid_array_clear(&ref);\n \tfree((void *)push_cert_nonce);\n+\tstring_list_clear(&proc_receive_refs, 0);\n \treturn 0;\n }\ndiff --git a/t/t5411/common-test-cases.sh b/t/t5411/common-test-cases.sh\nindex 7fa7211ce4..ff73549a8a 100644\n--- a/t/t5411/common-test-cases.sh\n+++ b/t/t5411/common-test-cases.sh\n@@ -53,6 +53,14 @@ test_expect_success \"cleanup\" '\n \t)\n '\n \n+test_expect_success \"add two receive.procReceiveRefs settings\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit config --add receive.procReceiveRefs refs/for &&\n+\t\tgit config --add receive.procReceiveRefs refs/review/\n+\t)\n+'\n+\n # Refs of upstream : master(A)\n # Refs of workbench: master(A)  tags/v123\n # git push         :                       next(A)  refs/for/master/topic(A)\n@@ -607,7 +615,7 @@ test_expect_success \"setup proc-receive hook (with extended status)\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n \ttest-tool proc-receive -v \\\n-\t\t-r \"$ZERO_OID $A refs/for/a/b/c/topic ok\" \\\n+\t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n \t\t-r \"$ZERO_OID $A refs/for/next/topic ok\\0ref=refs/pull/123/head\" \\\n \t\t-r \"$B $A refs/for/master/topic ok\\0ref=refs/pull/124/head forced-update\"\n \tEOF\n@@ -615,33 +623,33 @@ test_expect_success \"setup proc-receive hook (with extended status)\" '\n \n # Refs of upstream : master(A)\n # Refs of workbench: master(A)  tags/v123\n-# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+# git push         :                       refs/for/next/topic(A)  refs/review/a/b/c/topic(A)  refs/for/master/topic(A)\n test_expect_success \"proc-receive: report with extended status\" '\n \tgit -C workbench push origin \\\n \t\tHEAD:refs/for/next/topic \\\n-\t\tHEAD:refs/for/a/b/c/topic \\\n+\t\tHEAD:refs/review/a/b/c/topic \\\n \t\tHEAD:refs/for/master/topic \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n-\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tremote: # proc-receive hook\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n-\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n-\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic ok\n+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\\0ref=refs/pull/123/head\n \tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/master/topic ok\\0ref=refs/pull/124/head forced-update\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/pull/123/head\n-\t * [new reference] HEAD -> refs/for/a/b/c/topic\n+\t * [new reference] HEAD -> refs/review/a/b/c/topic\n \t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n@@ -714,3 +722,73 @@ test_expect_success \"proc-receive: report update of mixed refs\" '\n \tEOF\n \ttest_cmp expect actual\n '\n+\n+test_expect_success \"config add receive.procReceiveRefs = refs\" '\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"$B $A refs/heads/master ft\" \\\n+\t\t-r \"$A $ZERO_OID refs/heads/foo ft\" \\\n+\t\t-r \"$A $B refs/heads/bar ft\" \\\n+\t\t-r \"$A $B refs/for/master/topic ok\\0ref=refs/pull/123/head\" \\\n+\t\t-r \"$B $A refs/for/next/topic ok\\0ref=refs/pull/124/head forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"proc-receive: process all refs\" '\n+\tgit -C workbench push -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/heads/master ft\n+\tremote: proc-receive> <COMMIT-A> <ZERO-OID> refs/heads/foo ft\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/heads/bar ft\n+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/for/master/topic ok\\0ref=refs/pull/123/head\n+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/next/topic ok\\0ref=refs/pull/124/head forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/next/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> bar\n+\t - [deleted] foo\n+\t + <OID-B>...<OID-A> HEAD -> master (forced update)\n+\t <OID-A>..<OID-B> HEAD -> refs/pull/123/head\n+\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395357","messageId":"20200413164828.23260-6-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200412133022.17590-1-worldhello.net@gmail.com","subject":"[PATCH v11 5/7] refs.c: refactor to reuse ref_is_hidden()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-13T16:48:25Z","receivedAt":"2020-04-13T16:48:47Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd new function `ref_is_matched()` to reuse `ref_is_hidden()`. Will use\nthis function for `receive-pack` to check commands with specific\nprefixes.\n\nTest case t5512 covered this change.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n refs.c | 13 +++++++++----\n refs.h |  1 +\n 2 files changed, 10 insertions(+), 4 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 1ab0bb54d3..a20f56a463 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1388,14 +1388,14 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti\n \treturn 0;\n }\n \n-int ref_is_hidden(const char *refname, const char *refname_full)\n+int ref_matches(struct string_list *match_refs, const char *refname, const char *refname_full)\n {\n \tint i;\n \n-\tif (!hide_refs)\n+\tif (!match_refs)\n \t\treturn 0;\n-\tfor (i = hide_refs->nr - 1; i >= 0; i--) {\n-\t\tconst char *match = hide_refs->items[i].string;\n+\tfor (i = match_refs->nr - 1; i >= 0; i--) {\n+\t\tconst char *match = match_refs->items[i].string;\n \t\tconst char *subject;\n \t\tint neg = 0;\n \t\tconst char *p;\n@@ -1421,6 +1421,11 @@ int ref_is_hidden(const char *refname, const char *refname_full)\n \treturn 0;\n }\n \n+int ref_is_hidden(const char *refname, const char *refname_full)\n+{\n+\treturn ref_matches(hide_refs, refname, refname_full);\n+}\n+\n const char *find_descendant_ref(const char *dirname,\n \t\t\t\tconst struct string_list *extras,\n \t\t\t\tconst struct string_list *skip)\ndiff --git a/refs.h b/refs.h\nindex 545029c6d8..d02c72d3ad 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -739,6 +739,7 @@ int parse_hide_refs_config(const char *var, const char *value, const char *);\n  * parameter always points to the full ref name.\n  */\n int ref_is_hidden(const char *, const char *);\n+int ref_matches(struct string_list *, const char *, const char *);\n \n enum ref_type {\n \tREF_TYPE_PER_WORKTREE,\t  /* refs inside refs/ but not shared       */\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395359","messageId":"20200413164828.23260-8-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200412133022.17590-1-worldhello.net@gmail.com","subject":"[PATCH v11 7/7] doc: add documentation for the proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-13T16:48:27Z","receivedAt":"2020-04-13T16:48:49Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd documentation for the new \"proc-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/githooks.txt | 58 ++++++++++++++++++++++++++++++++++++++\n 1 file changed, 58 insertions(+)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 3dccab5375..1c528e2fc4 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -333,6 +333,64 @@ The default 'update' hook, when enabled--and with\n `hooks.allowunannotated` config option unset or set to false--prevents\n unannotated tags to be pushed.\n \n+proc-receive\n+~~~~~~~~~~~~\n+\n+This hook is invoked by linkgit:git-receive-pack[1].  If the server has\n+set the multi-valued config variable `receive.procReceiveRefs`, and the\n+commands sent to 'receive-pack' have matching reference names, these\n+commands will be executed by this hook, instead of by the internal\n+`execute_commands()` function.  This hook is responsible for updating\n+the relevant references and reporting the results back to 'receive-pack'.\n+\n+This hook executes once for the receive operation.  It takes no\n+arguments, but uses a pkt-line format protocol to communicate with\n+'receive-pack' to read commands, push-options and send results.  In the\n+following example for the protocol, the letter 'S' stands for\n+'receive-pack' and the letter 'H' stands for this hook.\n+\n+    # Version and features negotiation.\n+    S: PKT-LINE(version=1\\0push-options atomic...)\n+    S: flush-pkt\n+    H: PKT-LINE(version=1\\0push-options...)\n+    H: flush-pkt\n+\n+    # Send commands from server to the hook.\n+    S: PKT-LINE(<old-oid> <new-oid> <ref>)\n+    S: ... ...\n+    S: flush-pkt\n+    # Send push-options only if the 'push-options' feature is enabled.\n+    S: PKT-LINE(push-option)\n+    S: ... ...\n+    S: flush-pkt\n+\n+    # Receive result from the hook.\n+    # OK, run this command successfully.\n+    H: PKT-LINE(<old-oid> <new-oid> <ref> ok)\n+    # NO, I reject it.\n+    H: PKT-LINE(<old-oid> <new-oid> <ref> ng <reason>)\n+    # Fall through, let 'receive-pack' to execute it.\n+    H: PKT-LINE(<old-oid> <new-oid> <ref> ft)\n+    # OK, but has an alternate reference.  The alternate reference name\n+    # and other status are given in key=value pairs after the null\n+    # character.\n+    H: PKT-LINE(<old-oid> <new-oid> <ref> ok\\0ref=refs/pull/123/head\n+                forced-update)\n+    H: ... ...\n+    H: flush-pkt\n+\n+Each command for the 'proc-receive' hook may point to a pseudo-reference\n+and always has a zero-old as its old-oid, while the 'proc-receive' hook\n+may update an alternate reference and the alternate reference may exist\n+already with a non-zero old-oid.  For this case, this hook may return\n+different OID and different reference name as extended status of the\n+report line.\n+\n+The report of the commands of this hook should have the same order as\n+the input.  The exit status of the 'proc-receive' hook only determines\n+the success or failure of the group of commands sent to it, unless\n+atomic push is in use.\n+\n [[post-receive]]\n post-receive\n ~~~~~~~~~~~~\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395358","messageId":"20200413164828.23260-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200412133022.17590-1-worldhello.net@gmail.com","subject":"[PATCH v11 2/7] connect: export parse_feature_value()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-13T16:48:22Z","receivedAt":"2020-04-13T16:48:51Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nExport `parse_feature_value()` to parse extended status of report for\n\"proc-receive\" and \"receive-pack\".\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n connect.c | 3 +--\n connect.h | 1 +\n 2 files changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex b6451ab5e8..08e0616726 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -18,7 +18,6 @@\n \n static char *server_capabilities_v1;\n static struct argv_array server_capabilities_v2 = ARGV_ARRAY_INIT;\n-static const char *parse_feature_value(const char *, const char *, int *);\n \n static int check_ref(const char *name, unsigned int flags)\n {\n@@ -447,7 +446,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \treturn list;\n }\n \n-static const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp)\n+const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp)\n {\n \tint len;\n \ndiff --git a/connect.h b/connect.h\nindex 5f2382e018..17b3252cbc 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -12,6 +12,7 @@ int finish_connect(struct child_process *conn);\n int git_connection_is_socket(struct child_process *conn);\n int server_supports(const char *feature);\n int parse_feature_request(const char *features, const char *feature);\n+const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret);\n const char *server_feature_value(const char *feature, int *len_ret);\n int url_is_local_not_ssh(const char *url);\n \n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395360","messageId":"20200413164828.23260-5-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200412133022.17590-1-worldhello.net@gmail.com","subject":"[PATCH v11 4/7] send-pack: extension for client-side status report","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-13T16:48:24Z","receivedAt":"2020-04-13T16:48:53Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThis is the client-side extension for status report of git-push.\n\nThe \"proc-receive\" hook may receive a command for a pseudo-reference\nwith a zero-old as its old-oid, while the result of the hook may point\nto an alternate reference and the reference may exist already with a\nnon-zero old-oid.  Git client may receive a report with extened status\nas the following example:\n\n    ok refs/for/master/topic\\0ref=refs/pull/123/head old-oid=...\n\nParse the key-value pairs in the extended status after the null\ncharacter of the report, and show the proper reference name, old-oid\nand new-oid to the user.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n remote.c                     | 28 +++++++++++++++\n remote.h                     |  3 ++\n send-pack.c                  | 13 ++++++-\n t/t5411/common-test-cases.sh |  6 ++--\n transport-helper.c           | 66 +++++++++++++++++++-----------------\n transport.c                  | 33 +++++++++++++++---\n 6 files changed, 110 insertions(+), 39 deletions(-)\n\ndiff --git a/remote.c b/remote.c\nindex c43196ec06..0b22d9e14c 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"config.h\"\n+#include \"connect.h\"\n #include \"remote.h\"\n #include \"refs.h\"\n #include \"refspec.h\"\n@@ -2311,3 +2312,30 @@ void apply_push_cas(struct push_cas_option *cas,\n \tfor (ref = remote_refs; ref; ref = ref->next)\n \t\tapply_cas(cas, remote, ref);\n }\n+\n+void update_ref_from_remote_status(struct ref *ref)\n+{\n+\tchar *val;\n+\tchar c;\n+\tint len;\n+\n+\tif (!ref->remote_status)\n+\t\treturn;\n+\n+\tval = (char *)parse_feature_value(ref->remote_status, \"old-oid\", &len);\n+\tif (val && len) {\n+\t\tc = *(val+len);\n+\t\t*(val+len) = '\\0';\n+\t\tget_oid_hex(val, &ref->old_oid);\n+\t\t*(val+len) = c;\n+\t}\n+\tval = (char *)parse_feature_value(ref->remote_status, \"new-oid\", &len);\n+\tif (val && len) {\n+\t\tc = *(val+len);\n+\t\t*(val+len) = '\\0';\n+\t\tget_oid_hex(val, &ref->new_oid);\n+\t\t*(val+len) = c;\n+\t}\n+\tif (parse_feature_request(ref->remote_status, \"forced-update\"))\n+\t\tref->forced_update = 1;\n+}\ndiff --git a/remote.h b/remote.h\nindex 11d8719b58..09cf7014e3 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -345,4 +345,7 @@ int parseopt_push_cas_option(const struct option *, const char *arg, int unset);\n int is_empty_cas(const struct push_cas_option *);\n void apply_push_cas(struct push_cas_option *, struct remote *, struct ref *);\n \n+/* Parse key-value pairs of remote_status and update the reference accordingly */\n+void update_ref_from_remote_status(struct ref *ref);\n+\n #endif\ndiff --git a/send-pack.c b/send-pack.c\nindex 0407841ae8..ab294c9426 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -159,6 +159,9 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \twhile (1) {\n \t\tconst char *refname;\n \t\tchar *msg;\n+\t\tchar *extended_status = NULL;\n+\t\tint len;\n+\n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n \t\t\tbreak;\n \t\tif (!starts_with(reader->line, \"ok \") && !starts_with(reader->line, \"ng \")) {\n@@ -167,10 +170,13 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \t\t\tbreak;\n \t\t}\n \n+\t\tlen = strlen(reader->line);\n \t\trefname = reader->line + 3;\n \t\tmsg = strchr(refname, ' ');\n \t\tif (msg)\n \t\t\t*msg++ = '\\0';\n+\t\tif (reader->pktlen > len)\n+\t\t\textended_status = (char *)reader->line + len + 1;\n \n \t\t/* first try searching at our hint, falling back to all refs */\n \t\tif (hint)\n@@ -194,7 +200,12 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \t\t\thint->status = REF_STATUS_REMOTE_REJECT;\n \t\t\tret = -1;\n \t\t}\n-\t\thint->remote_status = xstrdup_or_null(msg);\n+\t\tif (msg) {\n+\t\t\thint->remote_status = xstrdup(msg);\n+\t\t} else if (extended_status) {\n+\t\t\thint->remote_status = xstrdup(extended_status);\n+\t\t\tupdate_ref_from_remote_status(hint);\n+\t\t}\n \t\t/* start our next search from the next ref */\n \t\thint = hint->next;\n \t}\ndiff --git a/t/t5411/common-test-cases.sh b/t/t5411/common-test-cases.sh\nindex fe970df755..7fa7211ce4 100644\n--- a/t/t5411/common-test-cases.sh\n+++ b/t/t5411/common-test-cases.sh\n@@ -640,9 +640,9 @@ test_expect_success \"proc-receive: report with extended status\" '\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \t * [new reference] HEAD -> refs/for/a/b/c/topic\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -701,7 +701,7 @@ test_expect_success \"proc-receive: report update of mixed refs\" '\n \t * [new branch] HEAD -> baz\n \t * [new reference] HEAD -> refs/for/next/topic\n \t * [new branch] HEAD -> foo\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 20a7185ec4..d2179631cf 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -747,37 +747,39 @@ static int push_update_ref_status(struct strbuf *buf,\n \t\t\tmsg = xstrdup(msg);\n \t\tstrbuf_release(&msg_buf);\n \n-\t\tif (!strcmp(msg, \"no match\")) {\n-\t\t\tstatus = REF_STATUS_NONE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"up to date\")) {\n-\t\t\tstatus = REF_STATUS_UPTODATE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"non-fast forward\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_NONFASTFORWARD;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"already exists\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_ALREADY_EXISTS;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"fetch first\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_FETCH_FIRST;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"needs force\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_NEEDS_FORCE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"stale info\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_STALE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"forced update\")) {\n-\t\t\tforced = 1;\n-\t\t\tFREE_AND_NULL(msg);\n+\t\tif (status != REF_STATUS_OK) {\n+\t\t\tif (!strcmp(msg, \"no match\")) {\n+\t\t\t\tstatus = REF_STATUS_NONE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"up to date\")) {\n+\t\t\t\tstatus = REF_STATUS_UPTODATE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"non-fast forward\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_NONFASTFORWARD;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"already exists\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_ALREADY_EXISTS;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"fetch first\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_FETCH_FIRST;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"needs force\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_NEEDS_FORCE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"stale info\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_STALE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"forced update\")) {\n+\t\t\t\tforced = 1;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n \t\t}\n \t}\n \n@@ -802,6 +804,8 @@ static int push_update_ref_status(struct strbuf *buf,\n \t(*ref)->status = status;\n \t(*ref)->forced_update |= forced;\n \t(*ref)->remote_status = msg;\n+\tif (status == REF_STATUS_OK && (*ref)->remote_status)\n+\t\tupdate_ref_from_remote_status(*ref);\n \treturn !(status == REF_STATUS_OK);\n }\n \ndiff --git a/transport.c b/transport.c\nindex 272c0f4046..b0451c7a3c 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -463,11 +463,30 @@ static void print_ref_status(char flag, const char *summary,\n \t\t\t     struct ref *to, struct ref *from, const char *msg,\n \t\t\t     int porcelain, int summary_width)\n {\n+\tchar *from_name = NULL;\n+\tchar *to_name = NULL;\n+\tconst char *val;\n+\tint len;\n+\n+\tif (from && from->remote_status) {\n+\t\tval = parse_feature_value(from->remote_status, \"ref\", &len);\n+\t\tif (val && len)\n+\t\t\t from_name = xmemdupz(val, len);\n+\t}\n+\tif (to && to->remote_status) {\n+\t\tval = parse_feature_value(to->remote_status, \"ref\", &len);\n+\t\tif (val && len)\n+\t\t\t to_name = xmemdupz(val, len);\n+\t}\n+\n \tif (porcelain) {\n \t\tif (from)\n-\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag,\n+\t\t\t\tfrom_name ? from_name : from->name,\n+\t\t\t\tto_name ? to_name: to->name);\n \t\telse\n-\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag,\n+\t\t\t\tto_name ? to_name: to->name);\n \t\tif (msg)\n \t\t\tfprintf(stdout, \"%s (%s)\\n\", summary, msg);\n \t\telse\n@@ -481,9 +500,11 @@ static void print_ref_status(char flag, const char *summary,\n \t\tfprintf(stderr, \" %s%c %-*s%s \", red, flag, summary_width,\n \t\t\tsummary, reset);\n \t\tif (from)\n-\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(to->name));\n+\t\t\tfprintf(stderr, \"%s -> %s\",\n+\t\t\t\tprettify_refname(from_name ? from_name : from->name),\n+\t\t\t\tprettify_refname(to_name ? to_name : to->name));\n \t\telse\n-\t\t\tfputs(prettify_refname(to->name), stderr);\n+\t\t\tfputs(prettify_refname(to_name ? to_name : to->name), stderr);\n \t\tif (msg) {\n \t\t\tfputs(\" (\", stderr);\n \t\t\tfputs(msg, stderr);\n@@ -491,6 +512,10 @@ static void print_ref_status(char flag, const char *summary,\n \t\t}\n \t\tfputc('\\n', stderr);\n \t}\n+\tif (from_name)\n+\t\tfree(from_name);\n+\tif (to_name)\n+\t\tfree(to_name);\n }\n \n static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_width)\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395372","messageId":"xmqq3697cax0.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200413105809.79988-1-zhiyou.jx@alibaba-inc.com","subject":"Re: [PATCH v10 2/8] receive-pack: add new proc-receive hook","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-04-13T21:50:51Z","receivedAt":"2020-04-13T21:51:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> Before making a decision, we must consider some limitations or backward\n> compatibility issues.  See the limitations from following code snippet\n> of \"send-pack.c\" and \"transport.c\":\n> ...\n> The sideband message stream (stderr of 'proc-receive') will be sent\n> directly to the client.  If the client is a very old version of git,\n> is is still safe?\n\nThese made your assumptions clear to me, I think.\n\nI was expecting that the protocol was purely between receive-pack\nand the hook and the communication back to the \"push\" side was done\nby receive-pack, AFTER receive-pack finished talking with the hook,\nreading and understanding what the hook did.  And with that\nexpectation, the protocol to the hook is free of \"compatibility\"\nconcern and does not have to be constrained to \"one report packet,\nwhich may have to carry multiple updates if the hook updates\nmultiple refs in response to a single update request packet\".\n\nBut if you are letting the hook talk directly to the \"push\" side,\nwithout \"receive-pack\" even snooping/understanding what is going on,\nsure, I can understand why you have to put such a limitation to the\nprotocol to the hook or use/unuse of the side-band.\n\nI actually think we would need some update (new capability, perhaps)\nto the protocol between \"push\" and \"receive-pack\" when we want to\nfully support things like \"you tried to push commit A to\n'refs/for/X'; 'refs/for/X' is not created nor updated, but instead\n'refs/heads/X' has been updated to commit B, which got created using\ninfo in A\".  A partial support can be done by just pretending that\nthe proposed update for 'refs/for/X' succeeded and nothing else\nhappened, and that may be a perfectly usable initial version.  But I\nwould suspect that we eventually would want to be able to tell the\n\"git push\" a bit more, and at that point, we'd probably want new\ncapabilities between \"push\" and \"receive-pack\".\n\nI think we should retain fairly strict control over the vocabulary\nused by \"receive-pack\" to explain what happened on \"the server side\"\nto the client, to avoid fracturing the ecosystem.  With \"hook's\noutput is sent pass-thru back to the client\" design, you'd allow a\nproprietary proc-receive hook to express what it did in a way that\nonly a matching proprietary variant of \"git push\" can understand,\nwhich I do not think is a good thing.\n\nThanks.\n"},{"id":"395402","messageId":"20200414123257.27449-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqq3697cax0.fsf@gitster.c.googlers.com","subject":"[PATCH v12 1/7] transport: not report a non-head push as a branch","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-14T12:32:51Z","receivedAt":"2020-04-14T12:33:19Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing a new reference (not a head or tag), report it as a new\nreference instead of a new branch.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411-proc-receive-hook.sh               | 67 +++++++++++++++++++\n t/t5411/common-functions.sh                | 50 ++++++++++++++\n t/t5411/common-test-cases.sh               | 43 ++++++++++++\n t/t5412-proc-receive-hook-http-protocol.sh | 78 ++++++++++++++++++++++\n t/t5516-fetch-push.sh                      |  2 +-\n transport.c                                |  9 ++-\n 6 files changed, 245 insertions(+), 4 deletions(-)\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/common-test-cases.sh\n create mode 100755 t/t5412-proc-receive-hook-http-protocol.sh\n\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nnew file mode 100755\nindex 0000000000..3f4bee76d9\n--- /dev/null\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -0,0 +1,67 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook'\n+\n+. ./test-lib.sh\n+\n+. \"$TEST_DIRECTORY\"/t5411/common-functions.sh\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  In addition to the common format method,\n+# we also replace the URL of different protocol for the upstream repository\n+# with a fixed pattern.\n+make_user_friendly_and_stable_output () {\n+\tmake_user_friendly_and_stable_output_common |\n+\t\tsed -e \"s#To ../upstream.git#To <URL/of/upstream.git>#\"\n+}\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+test_expect_success \"setup\" '\n+\tgit init --bare upstream.git &&\n+\tgit init workbench &&\n+\tcreate_commits_in workbench A B &&\n+\t(\n+\t\tcd workbench &&\n+\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\tgit config core.abbrev 7 &&\n+\t\tgit remote add origin ../upstream.git &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit tag -m \"v123\" v123 $A &&\n+\t\tgit push origin \\\n+\t\t\t$B:refs/heads/master \\\n+\t\t\t$A:refs/heads/next\n+\t) &&\n+\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t# setup pre-receive hook\n+\twrite_script upstream.git/hooks/pre-receive <<-\\EOF &&\n+\texec >&2\n+\techo \"# pre-receive hook\"\n+\twhile read old new ref\n+\tdo\n+\t\techo \"pre-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\t# setup post-receive hook\n+\twrite_script upstream.git/hooks/post-receive <<-\\EOF &&\n+\texec >&2\n+\techo \"# post-receive hook\"\n+\twhile read old new ref\n+\tdo\n+\t\techo \"post-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\tupstream=upstream.git\n+'\n+\n+# Include test cases for both file and HTTP protocol\n+. \"$TEST_DIRECTORY\"/t5411/common-test-cases.sh\n+\n+test_done\ndiff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\nnew file mode 100644\nindex 0000000000..4337b10c8c\n--- /dev/null\n+++ b/t/t5411/common-functions.sh\n@@ -0,0 +1,50 @@\n+# Create commits in <repo> and assign each commit's oid to shell variables\n+# given in the arguments (A, B, and C). E.g.:\n+#\n+#     create_commits_in <repo> A B C\n+#\n+# NOTE: Never calling this function from a subshell since variable\n+# assignments will disappear when subshell exits.\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{} --)\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  We can easily prepare the expect text\n+# without having to worry about future changes of the commit ID and spaces\n+# of the output.  We also replce single quotes with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect text.\n+make_user_friendly_and_stable_output_common () {\n+\tsed \\\n+\t\t-e \"s/  *\\$//\" \\\n+\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/'/\\\"/g\" \\\n+\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n+\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n+\t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n+\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n+\t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n+\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\"\n+}\ndiff --git a/t/t5411/common-test-cases.sh b/t/t5411/common-test-cases.sh\nnew file mode 100644\nindex 0000000000..23655846e4\n--- /dev/null\n+++ b/t/t5411/common-test-cases.sh\n@@ -0,0 +1,43 @@\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"normal git-push command\" '\n+\tgit -C workbench push -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tHEAD:refs/heads/master \\\n+\t\tHEAD:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo <URL/of/upstream.git>\n+\t + <OID-B>...<OID-A> HEAD -> master (forced update)\n+\t - [deleted] next\n+\t * [new tag] v123 -> v123\n+\t * [new reference] HEAD -> refs/review/master/topic\n+\t * [new branch] HEAD -> a/b/c\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5412-proc-receive-hook-http-protocol.sh b/t/t5412-proc-receive-hook-http-protocol.sh\nnew file mode 100755\nindex 0000000000..d102bc438b\n--- /dev/null\n+++ b/t/t5412-proc-receive-hook-http-protocol.sh\n@@ -0,0 +1,78 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook for HTTP protocol'\n+\n+. ./test-lib.sh\n+\n+ROOT_PATH=\"$PWD\"\n+. \"$TEST_DIRECTORY\"/lib-gpg.sh\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+. \"$TEST_DIRECTORY\"/lib-terminal.sh\n+start_httpd\n+\n+. \"$TEST_DIRECTORY\"/t5411/common-functions.sh\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  In addition to the common format method,\n+# we also replace the URL of different protocol for the upstream repository\n+# with a fixed pattern.\n+make_user_friendly_and_stable_output () {\n+\tmake_user_friendly_and_stable_output_common |\n+\t\tsed -e \"s#To http:.*/upstream.git#To <URL/of/upstream.git>#\"\n+}\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+test_expect_success \"setup\" '\n+\tgit init --bare upstream.git &&\n+\tgit -C upstream.git config http.receivepack true &&\n+\tgit init workbench &&\n+\tcreate_commits_in workbench A B &&\n+\t(\n+\t\tcd workbench &&\n+\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\tgit config core.abbrev 7 &&\n+\t\tgit remote add origin ../upstream.git &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit tag -m \"v123\" v123 $A &&\n+\t\tgit push origin \\\n+\t\t\t$B:refs/heads/master \\\n+\t\t\t$A:refs/heads/next\n+\t) &&\n+\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t# setup pre-receive hook\n+\twrite_script upstream.git/hooks/pre-receive <<-\\EOF &&\n+\texec >&2\n+\techo \"# pre-receive hook\"\n+\twhile read old new ref\n+\tdo\n+\t\techo \"pre-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\t# setup post-receive hook\n+\twrite_script upstream.git/hooks/post-receive <<-\\EOF &&\n+\texec >&2\n+\techo \"# post-receive hook\"\n+\twhile read old new ref\n+\tdo\n+\t\techo \"post-receive< $old $new $ref\"\n+\tdone\n+\tEOF\n+\n+\tupstream=\"$HTTPD_DOCUMENT_ROOT_PATH/upstream.git\" &&\n+\tmv upstream.git \"$upstream\" &&\n+\tgit -C workbench remote set-url origin $HTTPD_URL/smart/upstream.git\n+'\n+\n+setup_askpass_helper\n+\n+# Include test cases for both file and HTTP protocol\n+. \"$TEST_DIRECTORY\"/t5411/common-test-cases.sh\n+\n+test_done\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 9ff041a093..9e4b9313b5 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1039,7 +1039,7 @@ test_force_fetch_tag \"annotated tag\" \"-f -a -m'tag message'\"\n test_expect_success 'push --porcelain' '\n \tmk_empty testrepo &&\n \techo >.git/foo  \"To testrepo\" &&\n-\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new branch]\"  &&\n+\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new reference]\"  &&\n \techo >>.git/foo \"Done\" &&\n \tgit push >.git/bar --porcelain  testrepo refs/heads/master:refs/remotes/origin/master &&\n \t(\ndiff --git a/transport.c b/transport.c\nindex 1fdc7dac1a..272c0f4046 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -500,9 +500,12 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\t\t\t porcelain, summary_width);\n \telse if (is_null_oid(&ref->old_oid))\n \t\tprint_ref_status('*',\n-\t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n-\t\t\t\"[new branch]\"),\n-\t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t  ? \"[new tag]\"\n+\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t     ? \"[new branch]\"\n+\t\t\t\t     : \"[new reference]\")),\n+\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395403","messageId":"20200414123257.27449-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqq3697cax0.fsf@gitster.c.googlers.com","subject":"[PATCH v12 0/7] New proc-receive hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-14T12:32:50Z","receivedAt":"2020-04-14T12:33:20Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\n## Changes since v11\n\n* Change the syntax of status report from \"proc-receive\" to \"receive-pack\".\n\n        # Receive result from the hook.\n        # OK, run this command successfully.\n        H: PKT-LINE(ok <ref>)\n        # NO, I reject it.\n        H: PKT-LINE(ng <ref> <reason>)\n        # Fall through, let 'receive-pack' to execute it.\n        H: PKT-LINE(ft <ref>)\n        # OK, but has an alternate reference.  The alternate reference name\n        # is given in the third parameter, and other status can be given in\n        # key-value pairs.\n        H: PKT-LINE(alt <ref> <alt-ref> old-oid=<old-oid> new-oid=<new-oid>\n                    forced-update)\n        H: ... ...\n        H: flush-pkt\n\n* Support multiple responses from \"proc-receive\" for one reference.\n  Add test case:\n\n        test_expect_success \"setup proc-receive hook (multiple responses)\" '\n        \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n        \tprintf >&2 \"# proc-receive hook\\n\"\n        \ttest-tool proc-receive -v \\\n        \t\t-r \"alt refs/for/master/topic refs/changes/23/123/1\" \\\n        \t\t-r \"alt refs/for/master/topic refs/changes/24/124/2 old-oid=$A new-oid=$B\"\n        \tEOF\n        '\n        \n        # Refs of upstream : master(A)\n        # Refs of workbench: master(A)  tags/v123\n        # git push         :                       refs/for/master/topic(A)\n        test_expect_success \"proc-receive: report multiple response\" '\n        \tgit -C workbench push origin \\\n        \t\tHEAD:refs/for/master/topic \\\n        \t\t>out 2>&1 &&\n        \tmake_user_friendly_and_stable_output <out >actual &&\n        \tcat >expect <<-EOF &&\n        \tremote: # pre-receive hook\n        \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n        \tremote: # proc-receive hook\n        \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n        \tremote: proc-receive> alt refs/for/master/topic refs/changes/23/123/1\n        \tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/2 old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n        \tremote: # post-receive hook\n        \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n        \tTo <URL/of/upstream.git>\n        \t * [new reference] HEAD -> refs/changes/23/123/1\n        \t <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2\n        \tEOF\n        \ttest_cmp expect actual &&\n        \tgit -C \"$upstream\" show-ref >out &&\n        \tmake_user_friendly_and_stable_output <out >actual &&\n        \tcat >expect <<-EOF &&\n        \t<COMMIT-A> refs/heads/master\n        \tEOF\n        \ttest_cmp expect actual\n        '\n\n* \"receive-pack\" checks format of the report from \"proc-receive\", will\n  discard unknown status.\n\n* Apply the `SQUASH???` commit.\n\n\n## Range-diff v11..v12\n\n1:  bef4360e58 ! 1:  b76900cb0f receive-pack: add new proc-receive hook\n    @@ Commit message\n     \n             # Receive result from the hook.\n             # OK, run this command successfully.\n    -        H: PKT-LINE(<old-oid> <new-oid> <ref> ok)\n    +        H: PKT-LINE(ok <ref>)\n             # NO, I reject it.\n    -        H: PKT-LINE(<old-oid> <new-oid> <ref> ng <reason>)\n    +        H: PKT-LINE(ng <ref> <reason>)\n             # Fall through, let 'receive-pack' to execute it.\n    -        H: PKT-LINE(<old-oid> <new-oid> <ref> ft)\n    +        H: PKT-LINE(ft <ref>)\n             # OK, but has an alternate reference.  The alternate reference name\n    -        # and other status are given in key=value pairs after the null\n    -        # character.\n    -        H: PKT-LINE(<old-oid> <new-oid> <ref> ok\\0ref=refs/pull/123/head\n    +        # is given in the third parameter, and other status can be given in\n    +        # key-value pairs.\n    +        H: PKT-LINE(alt <ref> <alt-ref> old-oid=<old-oid> new-oid=<new-oid>\n                         forced-update)\n             H: ... ...\n             H: flush-pkt\n    @@ Commit message\n         other routines.  Finally, the result of the execution of these commands\n         will be reported to end user.\n     \n    -    The reporting function from \"receive-pack\" to \"send-pack\" is also\n    -    extended using a backward compatible way by adding key-value pairs\n    -    after an null character, like:\n    +    The reporting function from \"receive-pack\" to \"send-pack\" is extended\n    +    using a backward compatible way by adding key-value pairs after an null\n    +    character, like:\n     \n             # OK, run this command successfully with optional extended-status.\n             ok <reference>\\0ref=refs/pull/123/head old-oid=...\n    @@ Makefile: TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n      TEST_BUILTINS_OBJS += test-read-cache.o\n     \n      ## builtin/receive-pack.c ##\n    +@@\n    + #include \"remote.h\"\n    + #include \"connect.h\"\n    + #include \"string-list.h\"\n    ++#include \"string.h\"\n    + #include \"sha1-array.h\"\n    + #include \"connected.h\"\n    + #include \"argv-array.h\"\n     @@ builtin/receive-pack.c: static void write_head_info(void)\n      \tpacket_flush(1);\n      }\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\n     +\tfor (;;) {\n     +\t\tstruct object_id old_oid, new_oid;\n    -+\t\tconst char *refname;\n    -+\t\tconst char *p;\n     +\t\tchar *status;\n    -+\t\tchar *msg = NULL;\n    -+\t\tint len;\n    ++\t\tchar *refname;\n    ++\t\tchar *remains = NULL;\n     +\n     +\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n     +\t\t\tbreak;\n    -+\t\tlen = strlen(reader->line);\n    -+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n    -+\t\t    *p++ != ' ' ||\n    -+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n    -+\t\t    *p++ != ' ') {\n    -+\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\\n\",\n    -+\t\t\t\t    reader->line);\n    -+\t\t\tcode = -1;\n    -+\t\t\tcontinue;\n    ++\t\tstatus = (char *)reader->line;\n    ++\t\trefname = strchr(status, ' ');\n    ++\t\tif (refname) {\n    ++\t\t\t*refname++ = '\\0';\n    ++\t\t\tremains = strchr(refname, ' ');\n    ++\t\t\tif (remains)\n    ++\t\t\t\t*remains++ = '\\0';\n     +\t\t}\n    -+\n    -+\t\trefname = p;\n    -+\t\tstatus = strchr(p, ' ');\n    -+\t\tif (!status) {\n    -+\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'old new ref status [msg]', got '%s'\\n\",\n    ++\t\tif (!refname || strlen(refname) == 0) {\n    ++\t\t\tstrbuf_addf(errmsg, \"proc-receive expected '<status> <ref> ...', got '%s'\\n\",\n     +\t\t\t\t    reader->line);\n     +\t\t\tcode = -1;\n     +\t\t\tcontinue;\n     +\t\t}\n    -+\t\t*status++ = '\\0';\n    -+\t\tmsg = strchr(status, ' ');\n    -+\t\tif (msg)\n    -+\t\t\t*msg++ = '\\0';\n     +\n     +\t\t/* first try searching at our hint, falling back to all refs */\n     +\t\tif (hint)\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\t\tcontinue;\n     +\t\t}\n     +\t\tif (!strcmp(status, \"ng\")) {\n    -+\t\t\tif (msg)\n    -+\t\t\t\thint->error_string = xstrdup(msg);\n    ++\t\t\tif (remains)\n    ++\t\t\t\thint->error_string = xstrdup(remains);\n     +\t\t\telse\n     +\t\t\t\thint->error_string = \"failed\";\n     +\t\t\tcode = -1;\n     +\t\t} else if (!strcmp(\"ft\", status)) {\n     +\t\t\t/* Reset \"run_proc_receive\" field, and continue to run in \"receive-pack\" */\n     +\t\t\thint->run_proc_receive = 0;\n    ++\t\t} else if (!strcmp(\"alt\", status)) {\n    ++\t\t\tchar *alt_ref;\n    ++\n    ++\t\t\talt_ref = remains;\n    ++\t\t\tremains = strchr(alt_ref, ' ');\n    ++\t\t\tif (remains)\n    ++\t\t\t\t*remains++ = '\\0';\n    ++\n    ++\t\t\tif (hint->extended_status)\n    ++\t\t\t\tstrbuf_addstr(&extended_status, hint->extended_status);\n    ++\n    ++\t\t\tstrbuf_addf(&extended_status, \"%sref=%s\",\n    ++\t\t\t\t    extended_status.len > 0 ? \" \": \"\",\n    ++\t\t\t\t    alt_ref);\n    ++\n    ++\t\t\tif (remains) {\n    ++\t\t\t\tconst char *val;\n    ++\t\t\t\tint len;\n    ++\n    ++\t\t\t\tval = parse_feature_value(remains, \"old-oid\", &len);\n    ++\t\t\t\tif (val && len > 0)\n    ++\t\t\t\t\tif (!parse_oid_hex(val, &old_oid, &val))\n    ++\t\t\t\t\t\tstrbuf_addf(&extended_status, \"%sold-oid=%s\",\n    ++\t\t\t\t\t\t\t   extended_status.len > 0 ? \" \": \"\",\n    ++\t\t\t\t\t\t\t   oid_to_hex(&old_oid));\n    ++\n    ++\t\t\t\tval = parse_feature_value(remains, \"new-oid\", &len);\n    ++\t\t\t\tif (val && len > 0)\n    ++\t\t\t\t\tif (!parse_oid_hex(val, &new_oid, &val))\n    ++\t\t\t\t\t\tstrbuf_addf(&extended_status, \"%snew-oid=%s\",\n    ++\t\t\t\t\t\t\t   extended_status.len > 0 ? \" \": \"\",\n    ++\t\t\t\t\t\t\t   oid_to_hex(&new_oid));\n    ++\n    ++\t\t\t\tif (parse_feature_request(remains, \"forced-update\"))\n    ++\t\t\t\t\tstrbuf_addf(&extended_status, \"%sforced-update\",\n    ++\t\t\t\t\t\t    extended_status.len > 0 ? \" \": \"\");\n    ++\t\t\t}\n    ++\n    ++\t\t\tif (extended_status.len > 0) {\n    ++\t\t\t\tfree((void *)hint->extended_status);\n    ++\t\t\t\thint->extended_status = strbuf_detach(&extended_status, NULL);\n    ++\t\t\t}\n     +\t\t} else if (strcmp(\"ok\", status)) {\n     +\t\t\tstrbuf_addf(errmsg, \"proc-receive reported bad status '%s' on ref '%s'\\n\",\n     +\t\t\t\t    status, refname);\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\t\t/* Skip marking it as RUN_PROC_RECEIVE_RETURNED */\n     +\t\t\tcontinue;\n     +\t\t}\n    -+\t\tif (reader->pktlen > len)\n    -+\t\t\tstrbuf_addstr(&extended_status, (char *)reader->line + len + 1);\n    -+\t\tif (oidcmp(&hint->old_oid, &old_oid)) {\n    -+\t\t\toidcpy(&hint->old_oid, &old_oid);\n    -+\t\t\tstrbuf_addf(&extended_status, \"%sold-oid=%s\",\n    -+\t\t\t\t    extended_status.len > 0 ? \" \": \"\",\n    -+\t\t\t\t    oid_to_hex(&old_oid));\n    -+\t\t}\n    -+\t\tif (oidcmp(&hint->new_oid, &new_oid)) {\n    -+\t\t\toidcpy(&hint->new_oid, &new_oid);\n    -+\t\t\tstrbuf_addf(&extended_status, \"%snew-oid=%s\",\n    -+\t\t\t\t    extended_status.len > 0 ? \" \": \"\",\n    -+\t\t\t\t    oid_to_hex(&new_oid));\n    -+\t\t}\n    -+\t\tif (extended_status.len > 0)\n    -+\t\t\thint->extended_status = strbuf_detach(&extended_status, NULL);\n    ++\n     +\t\tif (hint->run_proc_receive)\n     +\t\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n     +\t}\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (bad oid)\" '\n    ++test_expect_success \"setup proc-receive hook (no ref)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"bad-id new-id ref ok\"\n    ++\t\t-r \"ok\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic\n    -+test_expect_success \"proc-receive: bad protocol (bad oid)\" '\n    ++test_expect_success \"proc-receive: bad protocol (no ref)\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic\\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> bad-id new-id ref ok\n    -+\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"bad-id new-id ref ok\"\n    -+\tTo <URL/of/upstream.git>\n    -+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n    -+\tEOF\n    -+\ttest_cmp expect actual &&\n    -+\tgit -C \"$upstream\" show-ref >out &&\n    -+\tmake_user_friendly_and_stable_output <out >actual &&\n    -+\tcat >expect <<-EOF &&\n    -+\t<COMMIT-A> refs/heads/master\n    -+\tEOF\n    -+\ttest_cmp expect actual\n    -+'\n    -+\n    -+test_expect_success \"setup proc-receive hook (no status)\" '\n    -+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    -+\tprintf >&2 \"# proc-receive hook\\n\"\n    -+\ttest-tool proc-receive -v \\\n    -+\t\t-r \"$ZERO_OID $A refs/for/master/topic\"\n    -+\tEOF\n    -+'\n    -+\n    -+# Refs of upstream : master(A)\n    -+# Refs of workbench: master(A)  tags/v123\n    -+# git push         :                       refs/for/master/topic\n    -+test_expect_success \"proc-receive: bad protocol (no status)\" '\n    -+\ttest_must_fail git -C workbench push origin \\\n    -+\t\tHEAD:refs/for/master/topic \\\n    -+\t\t>out 2>&1 &&\n    -+\tmake_user_friendly_and_stable_output <out >actual &&\n    -+\tcat >expect <<-EOF &&\n    -+\tremote: # pre-receive hook\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: # proc-receive hook\n    -+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: error: proc-receive expected \"old new ref status [msg]\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n    ++\tremote: proc-receive> ok\n    ++\tremote: error: proc-receive expected \"<status> <ref> ...\", got \"ok\"\n     +\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n     +\tEOF\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"$ZERO_OID $A refs/for/master/topic xx msg\"\n    ++\t\t-r \"xx refs/for/master/topic\"\n     +\tEOF\n     +'\n     +\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic xx msg\n    ++\tremote: proc-receive> xx refs/for/master/topic\n     +\tremote: error: proc-receive reported bad status \"xx\" on ref \"refs/for/master/topic\"\n     +\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng\"\n    ++\t\t-r \"ng refs/for/master/topic\"\n     +\tEOF\n     +'\n     +\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng\n    ++\tremote: proc-receive> ng refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n     +\tEOF\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"$ZERO_OID $A refs/for/master/topic ng error msg\"\n    ++\t\t-r \"ng refs/for/master/topic error msg\"\n     +\tEOF\n     +'\n     +\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng error msg\n    ++\tremote: proc-receive> ng refs/for/master/topic error msg\n     +\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n     +\tEOF\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"$ZERO_OID $A refs/heads/master ok\"\n    ++\t\t-r \"ok refs/heads/master\"\n     +\tEOF\n     +'\n     +\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/heads/master ok\n    ++\tremote: proc-receive> ok refs/heads/master\n     +\tremote: error: proc-receive reported status on unexpected ref: refs/heads/master\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"$ZERO_OID $A refs/for/master/topic ok\"\n    ++\t\t-r \"ok refs/for/master/topic\"\n     +\tEOF\n     +'\n     +\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n    ++\tremote: proc-receive> ok refs/for/master/topic\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n    ++\tremote: proc-receive> ok refs/for/master/topic\n     +\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (proc-receive failed to report status)\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive< issue=123\n     +\tremote: proc-receive< reviewer=user1\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok\n    ++\tremote: proc-receive> ok refs/for/master/topic\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"$ZERO_OID $B refs/for/master/topic ft\"\n    ++\t\t-r \"ft refs/for/master/topic\"\n     +\tEOF\n     +'\n     +\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-B> refs/for/master/topic ft\n    ++\tremote: proc-receive> ft refs/for/master/topic\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"$ZERO_OID $A refs/for/a/b/c/topic ok\" \\\n    -+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\\0ref=refs/pull/123/head\" \\\n    -+\t\t-r \"$B $A refs/for/master/topic ok\\0ref=refs/pull/124/head forced-update\"\n    ++\t\t-r \"ok refs/for/a/b/c/topic\" \\\n    ++\t\t-r \"alt refs/for/next/topic refs/pull/123/head\" \\\n    ++\t\t-r \"alt refs/for/master/topic refs/pull/124/head old-oid=$B forced-update new-oid=$A\"\n     +\tEOF\n     +'\n     +\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic ok\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\\0ref=refs/pull/123/head\n    -+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/master/topic ok\\0ref=refs/pull/124/head forced-update\n    ++\tremote: proc-receive> ok refs/for/a/b/c/topic\n    ++\tremote: proc-receive> alt refs/for/next/topic refs/pull/123/head\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/pull/124/head old-oid=<COMMIT-B> forced-update new-oid=<COMMIT-A>\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n    -+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n     +\t * [new reference] HEAD -> refs/for/next/topic\n     +\t * [new reference] HEAD -> refs/for/a/b/c/topic\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\ttest_cmp expect actual\n     +'\n     +\n    ++test_expect_success \"setup proc-receive hook (multiple responses)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"alt refs/for/master/topic refs/changes/23/123/1\" \\\n    ++\t\t-r \"alt refs/for/master/topic refs/changes/24/124/2 old-oid=$A new-oid=$B\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report multiple response\" '\n    ++\tgit -C workbench push origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/changes/23/123/1\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/2 old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t * [new reference] HEAD -> refs/for/master/topic\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n     +test_expect_success \"setup proc-receive hook\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"$ZERO_OID $A refs/for/next/topic ok\" \\\n    -+\t\t-r \"$A $B refs/for/master/topic ok\"\n    ++\t\t-r \"ok refs/for/next/topic\" \\\n    ++\t\t-r \"alt refs/for/master/topic refs/for/master/topic old-oid=$A new-oid=$B\"\n     +\tEOF\n     +'\n     +\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\n    -+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/for/master/topic ok\n    ++\tremote: proc-receive> ok refs/for/next/topic\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n    -+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n     +\t <OID-A>..<OID-B> <COMMIT-B> -> master\n     +\t * [new branch] HEAD -> bar\n2:  a4a1cc1d14 ! 2:  d20702291b send-pack: extension for client-side status report\n    @@ send-pack.c: static int receive_status(struct packet_reader *reader, struct ref\n      \t\t\tret = -1;\n      \t\t}\n     -\t\thint->remote_status = xstrdup_or_null(msg);\n    -+\t\tif (msg) {\n    ++\t\tif (msg)\n     +\t\t\thint->remote_status = xstrdup(msg);\n    -+\t\t} else if (extended_status) {\n    ++\t\telse if (extended_status)\n     +\t\t\thint->remote_status = xstrdup(extended_status);\n    -+\t\t\tupdate_ref_from_remote_status(hint);\n    -+\t\t}\n      \t\t/* start our next search from the next ref */\n      \t\thint = hint->next;\n      \t}\n    @@ send-pack.c: static int receive_status(struct packet_reader *reader, struct ref\n      ## t/t5411/common-test-cases.sh ##\n     @@ t/t5411/common-test-cases.sh: test_expect_success \"proc-receive: report with extended status\" '\n      \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n    - \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n      \tTo <URL/of/upstream.git>\n     -\t * [new reference] HEAD -> refs/for/next/topic\n     +\t * [new reference] HEAD -> refs/pull/123/head\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"proc-receive: report with ext\n      \tEOF\n      \ttest_cmp expect actual &&\n      \tgit -C \"$upstream\" show-ref >out &&\n    +@@ t/t5411/common-test-cases.sh: test_expect_success \"proc-receive: report multiple response\" '\n    + \tremote: # post-receive hook\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    + \tTo <URL/of/upstream.git>\n    +-\t * [new reference] HEAD -> refs/for/master/topic\n    ++\t * [new reference] HEAD -> refs/changes/23/123/1\n    ++\t <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2\n    + \tEOF\n    + \ttest_cmp expect actual &&\n    + \tgit -C \"$upstream\" show-ref >out &&\n     @@ t/t5411/common-test-cases.sh: test_expect_success \"proc-receive: report update of mixed refs\" '\n      \t * [new branch] HEAD -> baz\n      \t * [new reference] HEAD -> refs/for/next/topic\n    @@ transport-helper.c: static int push_update_ref_status(struct strbuf *buf,\n      \t\t}\n      \t}\n      \n    -@@ transport-helper.c: static int push_update_ref_status(struct strbuf *buf,\n    - \t(*ref)->status = status;\n    - \t(*ref)->forced_update |= forced;\n    - \t(*ref)->remote_status = msg;\n    -+\tif (status == REF_STATUS_OK && (*ref)->remote_status)\n    -+\t\tupdate_ref_from_remote_status(*ref);\n    - \treturn !(status == REF_STATUS_OK);\n    - }\n    - \n     \n      ## transport.c ##\n     @@ transport.c: static void print_ref_status(char flag, const char *summary,\n      \t\t\t     struct ref *to, struct ref *from, const char *msg,\n      \t\t\t     int porcelain, int summary_width)\n      {\n    -+\tchar *from_name = NULL;\n     +\tchar *to_name = NULL;\n     +\tconst char *val;\n     +\tint len;\n     +\n    -+\tif (from && from->remote_status) {\n    -+\t\tval = parse_feature_value(from->remote_status, \"ref\", &len);\n    -+\t\tif (val && len)\n    -+\t\t\t from_name = xmemdupz(val, len);\n    -+\t}\n     +\tif (to && to->remote_status) {\n     +\t\tval = parse_feature_value(to->remote_status, \"ref\", &len);\n     +\t\tif (val && len)\n    -+\t\t\t to_name = xmemdupz(val, len);\n    ++\t\tto_name = xmemdupz(val, len);\n     +\t}\n     +\n      \tif (porcelain) {\n      \t\tif (from)\n     -\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to->name);\n    -+\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag,\n    -+\t\t\t\tfrom_name ? from_name : from->name,\n    -+\t\t\t\tto_name ? to_name: to->name);\n    ++\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name,\n    ++\t\t\t\tto_name ? to_name : to->name);\n      \t\telse\n     -\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to->name);\n     +\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag,\n    -+\t\t\t\tto_name ? to_name: to->name);\n    ++\t\t\t\tto_name ? to_name : to->name);\n      \t\tif (msg)\n      \t\t\tfprintf(stdout, \"%s (%s)\\n\", summary, msg);\n      \t\telse\n    @@ transport.c: static void print_ref_status(char flag, const char *summary,\n      \t\tif (from)\n     -\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(to->name));\n     +\t\t\tfprintf(stderr, \"%s -> %s\",\n    -+\t\t\t\tprettify_refname(from_name ? from_name : from->name),\n    ++\t\t\t\tprettify_refname(from->name),\n     +\t\t\t\tprettify_refname(to_name ? to_name : to->name));\n      \t\telse\n     -\t\t\tfputs(prettify_refname(to->name), stderr);\n    @@ transport.c: static void print_ref_status(char flag, const char *summary,\n      \t\t}\n      \t\tfputc('\\n', stderr);\n      \t}\n    -+\tif (from_name)\n    -+\t\tfree(from_name);\n    -+\tif (to_name)\n    -+\t\tfree(to_name);\n    ++\tfree(to_name);\n      }\n      \n      static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_width)\n    +@@ transport.c: static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n    + \t}\n    + }\n    + \n    +-static int print_one_push_status(struct ref *ref, const char *dest, int count,\n    +-\t\t\t\t int porcelain, int summary_width)\n    ++static int _print_one_push_status(struct ref *ref, const char *dest, int count,\n    ++\t\t\t\t  int porcelain, int summary_width)\n    + {\n    + \tif (!count) {\n    + \t\tchar *url = transport_anonymize_url(dest);\n    +@@ transport.c: static int print_one_push_status(struct ref *ref, const char *dest, int count,\n    + \treturn 1;\n    + }\n    + \n    ++static int print_one_push_status(struct ref *ref, const char *dest, int count,\n    ++\t\t\t\t int porcelain, int summary_width)\n    ++{\n    ++\tchar *head;\n    ++\tchar *begin;\n    ++\tint n = 0;\n    ++\n    ++\tif (!ref->remote_status)\n    ++\t\treturn _print_one_push_status(ref, dest, count,\n    ++\t\t\t\t\t      porcelain, summary_width);\n    ++\n    ++\thead = ref->remote_status;\n    ++\tbegin = strstr(head, \"ref=\");\n    ++\tif (!begin)\n    ++\t\tbegin = ref->remote_status;\n    ++\tfor (;;) {\n    ++\t\tchar *end;\n    ++\t\tstruct object_id old_oid;\n    ++\t\tstruct object_id new_oid;\n    ++\t\tint forced_update;\n    ++\n    ++\t\tend = strstr(begin + 4, \"ref=\");\n    ++\t\tif (end)\n    ++\t\t\t*(end-1) = '\\0';\n    ++\n    ++\t\toidcpy(&old_oid, &ref->old_oid);\n    ++\t\toidcpy(&new_oid, &ref->new_oid);\n    ++\t\tforced_update = ref->forced_update;\n    ++\n    ++\t\tref->remote_status = begin;\n    ++\t\tupdate_ref_from_remote_status(ref);\n    ++\t\t_print_one_push_status(ref, dest, count + n++,\n    ++\t\t\t\t       porcelain, summary_width);\n    ++\n    ++\t\toidcpy(&ref->old_oid, &old_oid);\n    ++\t\toidcpy(&ref->new_oid, &new_oid);\n    ++\t\tref->forced_update = forced_update;\n    ++\n    ++\t\tif (end) {\n    ++\t\t\tbegin = end;\n    ++\t\t\t*(end-1) = ' ';\n    ++\t\t} else {\n    ++\t\t\tbreak;\n    ++\t\t}\n    ++\t}\n    ++\tref->remote_status = head;\n    ++\treturn n;\n    ++}\n    ++\n    + static int measure_abbrev(const struct object_id *oid, int sofar)\n    + {\n    + \tchar hex[GIT_MAX_HEXSZ + 1];\n3:  4e3c383c12 = 3:  a8860faa17 refs.c: refactor to reuse ref_is_hidden()\n4:  dd83569c4e ! 4:  46a839ddf6 receive-pack: new config receive.procReceiveRefs\n    @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n     +\tif (proc_receive_refs.nr > 0) {\n     +\t\tstruct strbuf refname_full = STRBUF_INIT;\n     +\t\tsize_t prefix_len;\n    ++\n    ++\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n    ++\t\tprefix_len = refname_full.len;\n      \n     -\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n     -\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n     -\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n     -\t\t\trun_proc_receive = 1;\n    -+\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n    -+\t\tprefix_len = refname_full.len;\n    -+\n     +\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n     +\t\t\tif (!should_process_cmd(cmd))\n     +\t\t\t\tcontinue;\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"setup proc-receive hook (with\n      \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n      \tprintf >&2 \"# proc-receive hook\\n\"\n      \ttest-tool proc-receive -v \\\n    --\t\t-r \"$ZERO_OID $A refs/for/a/b/c/topic ok\" \\\n    -+\t\t-r \"$ZERO_OID $A refs/review/a/b/c/topic ok\" \\\n    - \t\t-r \"$ZERO_OID $A refs/for/next/topic ok\\0ref=refs/pull/123/head\" \\\n    - \t\t-r \"$B $A refs/for/master/topic ok\\0ref=refs/pull/124/head forced-update\"\n    +-\t\t-r \"ok refs/for/a/b/c/topic\" \\\n    ++\t\t-r \"ok refs/review/a/b/c/topic\" \\\n    + \t\t-r \"alt refs/for/next/topic refs/pull/123/head\" \\\n    + \t\t-r \"alt refs/for/master/topic refs/pull/124/head old-oid=$B forced-update new-oid=$A\"\n      \tEOF\n     @@ t/t5411/common-test-cases.sh: test_expect_success \"setup proc-receive hook (with extended status)\" '\n      \n    @@ t/t5411/common-test-cases.sh: test_expect_success \"setup proc-receive hook (with\n     -\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n      \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    --\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic ok\n    -+\tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic ok\n    - \tremote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/next/topic ok\\0ref=refs/pull/123/head\n    - \tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/master/topic ok\\0ref=refs/pull/124/head forced-update\n    +-\tremote: proc-receive> ok refs/for/a/b/c/topic\n    ++\tremote: proc-receive> ok refs/review/a/b/c/topic\n    + \tremote: proc-receive> alt refs/for/next/topic refs/pull/123/head\n    + \tremote: proc-receive> alt refs/for/master/topic refs/pull/124/head old-oid=<COMMIT-B> forced-update new-oid=<COMMIT-A>\n      \tremote: # post-receive hook\n      \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n     -\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n    - \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n      \tTo <URL/of/upstream.git>\n      \t * [new reference] HEAD -> refs/pull/123/head\n     -\t * [new reference] HEAD -> refs/for/a/b/c/topic\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"proc-receive: report update o\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"$B $A refs/heads/master ft\" \\\n    -+\t\t-r \"$A $ZERO_OID refs/heads/foo ft\" \\\n    -+\t\t-r \"$A $B refs/heads/bar ft\" \\\n    -+\t\t-r \"$A $B refs/for/master/topic ok\\0ref=refs/pull/123/head\" \\\n    -+\t\t-r \"$B $A refs/for/next/topic ok\\0ref=refs/pull/124/head forced-update\"\n    ++\t\t-r \"ft refs/heads/master\" \\\n    ++\t\t-r \"ft refs/heads/foo\" \\\n    ++\t\t-r \"ft refs/heads/bar\" \\\n    ++\t\t-r \"alt refs/for/master/topic refs/pull/123/head old-oid=$A new-oid=$B \" \\\n    ++\t\t-r \"alt refs/for/next/topic refs/pull/124/head old-oid=$B new-oid=$A forced-update\"\n     +\tEOF\n     +'\n     +\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"proc-receive: report update o\n     +\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    -+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/heads/master ft\n    -+\tremote: proc-receive> <COMMIT-A> <ZERO-OID> refs/heads/foo ft\n    -+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/heads/bar ft\n    -+\tremote: proc-receive> <COMMIT-A> <COMMIT-B> refs/for/master/topic ok\\0ref=refs/pull/123/head\n    -+\tremote: proc-receive> <COMMIT-B> <COMMIT-A> refs/for/next/topic ok\\0ref=refs/pull/124/head forced-update\n    ++\tremote: proc-receive> ft refs/heads/master\n    ++\tremote: proc-receive> ft refs/heads/foo\n    ++\tremote: proc-receive> ft refs/heads/bar\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    ++\tremote: proc-receive> alt refs/for/next/topic refs/pull/124/head old-oid=<COMMIT-B> new-oid=<COMMIT-A> forced-update\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n     +\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n     +\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n    -+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n    -+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/next/topic\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n     +\tTo <URL/of/upstream.git>\n     +\t <OID-A>..<OID-B> <COMMIT-B> -> bar\n     +\t - [deleted] foo\n5:  2d04f495a3 ! 5:  a357234e97 doc: add documentation for the proc-receive hook\n    @@ Documentation/githooks.txt: The default 'update' hook, when enabled--and with\n     +\n     +    # Receive result from the hook.\n     +    # OK, run this command successfully.\n    -+    H: PKT-LINE(<old-oid> <new-oid> <ref> ok)\n    ++    H: PKT-LINE(ok <ref>)\n     +    # NO, I reject it.\n    -+    H: PKT-LINE(<old-oid> <new-oid> <ref> ng <reason>)\n    ++    H: PKT-LINE(ng <ref> <reason>)\n     +    # Fall through, let 'receive-pack' to execute it.\n    -+    H: PKT-LINE(<old-oid> <new-oid> <ref> ft)\n    ++    H: PKT-LINE(ft <ref>)\n     +    # OK, but has an alternate reference.  The alternate reference name\n    -+    # and other status are given in key=value pairs after the null\n    -+    # character.\n    -+    H: PKT-LINE(<old-oid> <new-oid> <ref> ok\\0ref=refs/pull/123/head\n    ++    # is given in the third parameter, and other status can be given in\n    ++    # key-value pairs.\n    ++    H: PKT-LINE(alt <ref> <alt-ref> old-oid=<old-oid> new-oid=<new-oid>\n     +                forced-update)\n     +    H: ... ...\n     +    H: flush-pkt\n\n---\n\nJiang Xin (7):\n  transport: not report a non-head push as a branch\n  connect: export parse_feature_value()\n  receive-pack: add new proc-receive hook\n  send-pack: extension for client-side status report\n  refs.c: refactor to reuse ref_is_hidden()\n  receive-pack: new config receive.procReceiveRefs\n  doc: add documentation for the proc-receive hook\n\n Documentation/config/receive.txt           |  14 +\n Documentation/githooks.txt                 |  58 ++\n Makefile                                   |   1 +\n builtin/receive-pack.c                     | 345 ++++++++-\n connect.c                                  |   3 +-\n connect.h                                  |   1 +\n refs.c                                     |  13 +-\n refs.h                                     |   1 +\n remote.c                                   |  28 +\n remote.h                                   |   3 +\n send-pack.c                                |  11 +-\n t/helper/test-proc-receive.c               | 178 +++++\n t/helper/test-tool.c                       |   1 +\n t/helper/test-tool.h                       |   1 +\n t/t5411-proc-receive-hook.sh               |  67 ++\n t/t5411/common-functions.sh                |  54 ++\n t/t5411/common-test-cases.sh               | 798 +++++++++++++++++++++\n t/t5412-proc-receive-hook-http-protocol.sh |  78 ++\n t/t5516-fetch-push.sh                      |   2 +-\n transport-helper.c                         |  64 +-\n transport.c                                |  85 ++-\n 21 files changed, 1749 insertions(+), 57 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/common-test-cases.sh\n create mode 100755 t/t5412-proc-receive-hook-http-protocol.sh\n\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395404","messageId":"20200414123257.27449-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqq3697cax0.fsf@gitster.c.googlers.com","subject":"[PATCH v12 2/7] connect: export parse_feature_value()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-14T12:32:52Z","receivedAt":"2020-04-14T12:33:22Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nExport `parse_feature_value()` to parse extended status of report for\n\"proc-receive\" and \"receive-pack\".\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n connect.c | 3 +--\n connect.h | 1 +\n 2 files changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex b6451ab5e8..08e0616726 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -18,7 +18,6 @@\n \n static char *server_capabilities_v1;\n static struct argv_array server_capabilities_v2 = ARGV_ARRAY_INIT;\n-static const char *parse_feature_value(const char *, const char *, int *);\n \n static int check_ref(const char *name, unsigned int flags)\n {\n@@ -447,7 +446,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \treturn list;\n }\n \n-static const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp)\n+const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp)\n {\n \tint len;\n \ndiff --git a/connect.h b/connect.h\nindex 5f2382e018..17b3252cbc 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -12,6 +12,7 @@ int finish_connect(struct child_process *conn);\n int git_connection_is_socket(struct child_process *conn);\n int server_supports(const char *feature);\n int parse_feature_request(const char *features, const char *feature);\n+const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret);\n const char *server_feature_value(const char *feature, int *len_ret);\n int url_is_local_not_ssh(const char *url);\n \n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395406","messageId":"20200414123257.27449-4-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqq3697cax0.fsf@gitster.c.googlers.com","subject":"[PATCH v12 3/7] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-14T12:32:53Z","receivedAt":"2020-04-14T12:33:25Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nGit calls an internal `execute_commands` function to handle commands\nsent from client to `git-receive-pack`.  Regardless of what references\nthe user pushes, git creates or updates the corresponding references if\nthe user has write-permission.  A contributor who has no\nwrite-permission, cannot push to the repository directly.  So, the\ncontributor has to write commits to an alternate location, and sends\npull request by emails or by other ways.  We call this workflow as a\ndistributed workflow.\n\nIt would be more convenient to work in a centralized workflow like what\nGerrit provided for some cases.  For example, a read-only user who\ncannot push to a branch directly can run the following `git push`\ncommand to push commits to a pseudo reference (has a prefix \"refs/for/\",\nnot \"refs/heads/\") to create a code review.\n\n    git push origin \\\n        HEAD:refs/for/<branch-name>/<session>\n\nThe `<branch-name>` in the above example can be as simple as \"master\",\nor a more complicated branch name like \"foo/bar\".  The `<session>` in\nthe above example command can be the local branch name of the client\nside, such as \"my/topic\".\n\nWe cannot implement a centralized workflow elegantly by using\n\"pre-receive\" + \"post-receive\", because Git will call the internal\nfunction \"execute_commands\" to create references (even the special\npseudo reference) between these two hooks.  Even though we can delete\nthe temporarily created pseudo reference via the \"post-receive\" hook,\nhaving a temporary reference is not safe for concurrent pushes.\n\nSo, add a filter and a new handler to support this kind of workflow.\nThe filter will check the prefix of the reference name, and if the\ncommand has a special reference name, the filter will turn a specific\nfield (`run_proc_receive`) on for the command.  Commands with this filed\nturned on will be executed by a new handler (an hook named\n\"proc-receive\") instead of the internal `execute_commands` function.\nWe can use this \"proc-receive\" command to create pull requests or send\nemails for code review.\n\nSuggested by Junio, this \"proc-receive\" hook reads the commands,\npush-options (optional), and send result using a protocol in pkt-line\nformat.  In the following example, The letter \"S\" stands for\n\"receive-pack\" and letter \"H\" stands for the hook.\n\n    # Version and features negotiation.\n    S: PKT-LINE(version=1\\0push-options atomic...)\n    S: flush-pkt\n    H: PKT-LINE(version=1\\0push-options...)\n    H: flush-pkt\n\n    # Send commands from server to the hook.\n    S: PKT-LINE(<old-oid> <new-oid> <ref>)\n    S: ... ...\n    S: flush-pkt\n    # Send push-options only if the 'push-options' feature is enabled.\n    S: PKT-LINE(push-option)\n    S: ... ...\n    S: flush-pkt\n\n    # Receive result from the hook.\n    # OK, run this command successfully.\n    H: PKT-LINE(ok <ref>)\n    # NO, I reject it.\n    H: PKT-LINE(ng <ref> <reason>)\n    # Fall through, let 'receive-pack' to execute it.\n    H: PKT-LINE(ft <ref>)\n    # OK, but has an alternate reference.  The alternate reference name\n    # is given in the third parameter, and other status can be given in\n    # key-value pairs.\n    H: PKT-LINE(alt <ref> <alt-ref> old-oid=<old-oid> new-oid=<new-oid>\n                forced-update)\n    H: ... ...\n    H: flush-pkt\n\nAfter receiving a command, the hook will execute the command, and may\ncreate/update different reference.  For example, a command for a pseudo\nreference \"refs/for/master/topic\" may create/update different reference\nsuch as \"refs/pull/123/head\".  The alternate reference name and other\nstatus are given in key-value pairs as extended status of the report\nline.\n\nThe list of commands returned from \"proc-receive\" will replace the\nrelevant commands that are sent from user to \"receive-pack\", and\n\"receive-pack\" will continue to run the \"execute_commands\" function and\nother routines.  Finally, the result of the execution of these commands\nwill be reported to end user.\n\nThe reporting function from \"receive-pack\" to \"send-pack\" is extended\nusing a backward compatible way by adding key-value pairs after an null\ncharacter, like:\n\n    # OK, run this command successfully with optional extended-status.\n    ok <reference>\\0ref=refs/pull/123/head old-oid=...\n\n    # NO, I reject it.\n    ng <reference> <error message>\n\nParsing the extended status of the report lines for \"send-pack\" is not\nimplemented in this commit, so we can make a compatible test on old\nversion of a Git client.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Makefile                     |   1 +\n builtin/receive-pack.c       | 316 +++++++++++++++-\n t/helper/test-proc-receive.c | 178 +++++++++\n t/helper/test-tool.c         |   1 +\n t/helper/test-tool.h         |   1 +\n t/t5411/common-functions.sh  |  10 +-\n t/t5411/common-test-cases.sh | 676 +++++++++++++++++++++++++++++++++++\n 7 files changed, 1171 insertions(+), 12 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n\ndiff --git a/Makefile b/Makefile\nindex ef1ff2228f..1c52c280ce 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -725,6 +725,7 @@ TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\n+TEST_BUILTINS_OBJS += test-proc-receive.o\n TEST_BUILTINS_OBJS += test-progress.o\n TEST_BUILTINS_OBJS += test-reach.o\n TEST_BUILTINS_OBJS += test-read-cache.o\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 2cc18bbffd..fbbb01135e 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -13,6 +13,7 @@\n #include \"remote.h\"\n #include \"connect.h\"\n #include \"string-list.h\"\n+#include \"string.h\"\n #include \"sha1-array.h\"\n #include \"connected.h\"\n #include \"argv-array.h\"\n@@ -308,11 +309,15 @@ static void write_head_info(void)\n \tpacket_flush(1);\n }\n \n+#define RUN_PROC_RECEIVE_SCHEDULED\t1\n+#define RUN_PROC_RECEIVE_RETURNED\t2\n struct command {\n \tstruct command *next;\n \tconst char *error_string;\n+\tconst char *extended_status;\n \tunsigned int skip_update:1,\n-\t\t     did_not_exist:1;\n+\t\t     did_not_exist:1,\n+\t\t     run_proc_receive:2;\n \tint index;\n \tstruct object_id old_oid;\n \tstruct object_id new_oid;\n@@ -817,6 +822,266 @@ static int run_update_hook(struct command *cmd)\n \treturn finish_command(&proc);\n }\n \n+static struct command *find_command_by_refname(const struct command *list,\n+\t\t\t\t\t       const char *refname)\n+{\n+\tfor (; list; list = list->next)\n+\t\tif (!strcmp(list->ref_name, refname))\n+\t\t\treturn (struct command *)list;\n+\treturn NULL;\n+}\n+\n+static int read_proc_receive_report(struct packet_reader *reader,\n+\t\t\t\t    struct command *commands,\n+\t\t\t\t    struct strbuf *errmsg)\n+{\n+\tstruct command *cmd;\n+\tstruct command *hint = NULL;\n+\tstruct strbuf extended_status = STRBUF_INIT;\n+\tint code = 0;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tchar *status;\n+\t\tchar *refname;\n+\t\tchar *remains = NULL;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\t\tstatus = (char *)reader->line;\n+\t\trefname = strchr(status, ' ');\n+\t\tif (refname) {\n+\t\t\t*refname++ = '\\0';\n+\t\t\tremains = strchr(refname, ' ');\n+\t\t\tif (remains)\n+\t\t\t\t*remains++ = '\\0';\n+\t\t}\n+\t\tif (!refname || strlen(refname) == 0) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive expected '<status> <ref> ...', got '%s'\\n\",\n+\t\t\t\t    reader->line);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/* first try searching at our hint, falling back to all refs */\n+\t\tif (hint)\n+\t\t\thint = find_command_by_refname(hint, refname);\n+\t\tif (!hint)\n+\t\t\thint = find_command_by_refname(commands, refname);\n+\t\tif (!hint) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unknown ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!hint->run_proc_receive) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unexpected ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(status, \"ng\")) {\n+\t\t\tif (remains)\n+\t\t\t\thint->error_string = xstrdup(remains);\n+\t\t\telse\n+\t\t\t\thint->error_string = \"failed\";\n+\t\t\tcode = -1;\n+\t\t} else if (!strcmp(\"ft\", status)) {\n+\t\t\t/* Reset \"run_proc_receive\" field, and continue to run in \"receive-pack\" */\n+\t\t\thint->run_proc_receive = 0;\n+\t\t} else if (!strcmp(\"alt\", status)) {\n+\t\t\tchar *alt_ref;\n+\n+\t\t\talt_ref = remains;\n+\t\t\tremains = strchr(alt_ref, ' ');\n+\t\t\tif (remains)\n+\t\t\t\t*remains++ = '\\0';\n+\n+\t\t\tif (hint->extended_status)\n+\t\t\t\tstrbuf_addstr(&extended_status, hint->extended_status);\n+\n+\t\t\tstrbuf_addf(&extended_status, \"%sref=%s\",\n+\t\t\t\t    extended_status.len > 0 ? \" \": \"\",\n+\t\t\t\t    alt_ref);\n+\n+\t\t\tif (remains) {\n+\t\t\t\tconst char *val;\n+\t\t\t\tint len;\n+\n+\t\t\t\tval = parse_feature_value(remains, \"old-oid\", &len);\n+\t\t\t\tif (val && len > 0)\n+\t\t\t\t\tif (!parse_oid_hex(val, &old_oid, &val))\n+\t\t\t\t\t\tstrbuf_addf(&extended_status, \"%sold-oid=%s\",\n+\t\t\t\t\t\t\t   extended_status.len > 0 ? \" \": \"\",\n+\t\t\t\t\t\t\t   oid_to_hex(&old_oid));\n+\n+\t\t\t\tval = parse_feature_value(remains, \"new-oid\", &len);\n+\t\t\t\tif (val && len > 0)\n+\t\t\t\t\tif (!parse_oid_hex(val, &new_oid, &val))\n+\t\t\t\t\t\tstrbuf_addf(&extended_status, \"%snew-oid=%s\",\n+\t\t\t\t\t\t\t   extended_status.len > 0 ? \" \": \"\",\n+\t\t\t\t\t\t\t   oid_to_hex(&new_oid));\n+\n+\t\t\t\tif (parse_feature_request(remains, \"forced-update\"))\n+\t\t\t\t\tstrbuf_addf(&extended_status, \"%sforced-update\",\n+\t\t\t\t\t\t    extended_status.len > 0 ? \" \": \"\");\n+\t\t\t}\n+\n+\t\t\tif (extended_status.len > 0) {\n+\t\t\t\tfree((void *)hint->extended_status);\n+\t\t\t\thint->extended_status = strbuf_detach(&extended_status, NULL);\n+\t\t\t}\n+\t\t} else if (strcmp(\"ok\", status)) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported bad status '%s' on ref '%s'\\n\",\n+\t\t\t\t    status, refname);\n+\t\t\tcode = -1;\n+\t\t\t/* Skip marking it as RUN_PROC_RECEIVE_RETURNED */\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (hint->run_proc_receive)\n+\t\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n+\t}\n+\n+\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\tif (cmd->run_proc_receive && !cmd->error_string &&\n+\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {\n+\t\t    cmd->error_string = \"proc-receive failed to report status\";\n+\t\t    code = -1;\n+\t\t}\n+\n+\treturn code;\n+}\n+\n+static int run_proc_receive_hook(struct command *commands,\n+\t\t\t\t const struct string_list *push_options)\n+{\n+\tstruct child_process proc = CHILD_PROCESS_INIT;\n+\tstruct async muxer;\n+\tstruct command *cmd;\n+\tconst char *argv[2];\n+\tstruct packet_reader reader;\n+\tstruct strbuf cap = STRBUF_INIT;\n+\tstruct strbuf errmsg = STRBUF_INIT;\n+\tint pr_use_push_options = 0;\n+\tint version = 0;\n+\tint code;\n+\n+\targv[0] = find_hook(\"proc-receive\");\n+\tif (!argv[0]) {\n+\t\trp_error(\"cannot find hook 'proc-receive'\");\n+\t\treturn -1;\n+\t}\n+\targv[1] = NULL;\n+\n+\tproc.argv = argv;\n+\tproc.in = -1;\n+\tproc.out = -1;\n+\tproc.trace2_hook_name = \"proc-receive\";\n+\n+\tif (use_sideband) {\n+\t\tmemset(&muxer, 0, sizeof(muxer));\n+\t\tmuxer.proc = copy_to_sideband;\n+\t\tmuxer.in = -1;\n+\t\tcode = start_async(&muxer);\n+\t\tif (code)\n+\t\t\treturn code;\n+\t\tproc.err = muxer.in;\n+\t} else {\n+\t\tproc.err = 0;\n+\t}\n+\n+\tcode = start_command(&proc);\n+\tif (code) {\n+\t\tif (use_sideband)\n+\t\t\tfinish_async(&muxer);\n+\t\treturn code;\n+\t}\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\n+\t/* Version negotiaton */\n+\tpacket_reader_init(&reader, proc.out, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\tif (use_atomic)\n+\t\tstrbuf_addstr(&cap, \" atomic\");\n+\tif (use_push_options)\n+\t\tstrbuf_addstr(&cap, \" push-options\");\n+\tif (cap.len) {\n+\t\tpacket_write_fmt(proc.in, \"version=1%c%s\\n\", '\\0', cap.buf + 1);\n+\t\tstrbuf_release(&cap);\n+\t} else {\n+\t\tpacket_write_fmt(proc.in, \"version=1\\n\");\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader.pktlen > 8 && starts_with(reader.line, \"version=\")) {\n+\t\t\tversion = atoi(reader.line + 8);\n+\t\t\tlinelen = strlen(reader.line);\n+\t\t\tif (linelen < reader.pktlen) {\n+\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tpr_use_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (version != 1) {\n+\t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n+\t\t\t    version);\n+\t\tcode = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\t/* Send commands */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)\n+\t\t\tcontinue;\n+\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n+\t\t\t\t oid_to_hex(&cmd->old_oid),\n+\t\t\t\t oid_to_hex(&cmd->new_oid),\n+\t\t\t\t cmd->ref_name);\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\t/* Send push options */\n+\tif (pr_use_push_options) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tfor_each_string_list_item(item, push_options)\n+\t\t\tpacket_write_fmt(proc.in, \"%s\", item->string);\n+\t\tpacket_flush(proc.in);\n+\t}\n+\n+\t/* Read result from proc-receive */\n+\tcode = read_proc_receive_report(&reader, commands, &errmsg);\n+\n+cleanup:\n+\tclose(proc.in);\n+\tclose(proc.out);\n+\tif (use_sideband)\n+\t\tfinish_async(&muxer);\n+\tif (finish_command(&proc))\n+\t\tcode = -1;\n+\tif (errmsg.len >0) {\n+\t\tchar *p = errmsg.buf;\n+\n+\t\tp += errmsg.len - 1;\n+\t\tif (*p == '\\n')\n+\t\t\t*p = '\\0';\n+\t\trp_error(\"%s\", errmsg.buf);\n+\t\tstrbuf_release(&errmsg);\n+\t}\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn code;\n+}\n+\n static char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n@@ -1392,7 +1657,7 @@ static void execute_commands_non_atomic(struct command *commands,\n \tstruct strbuf err = STRBUF_INIT;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\ttransaction = ref_transaction_begin(&err);\n@@ -1432,7 +1697,7 @@ static void execute_commands_atomic(struct command *commands,\n \t}\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\tcmd->error_string = update(cmd, si);\n@@ -1468,6 +1733,7 @@ static void execute_commands(struct command *commands,\n \tstruct iterate_data data;\n \tstruct async muxer;\n \tint err_fd = 0;\n+\tint run_proc_receive = 0;\n \n \tif (unpacker_error) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n@@ -1497,6 +1763,21 @@ static void execute_commands(struct command *commands,\n \n \treject_updates_to_hidden(commands);\n \n+\t/*\n+\t * Try to find commands that have special prefix in their reference names,\n+\t * and mark them to run an external \"proc-receive\" hook later.\n+\t */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!should_process_cmd(cmd))\n+\t\t\tcontinue;\n+\n+\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n+\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n+\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\trun_proc_receive = 1;\n+\t\t}\n+\t}\n+\n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n \t\t\tif (!cmd->error_string)\n@@ -1523,6 +1804,12 @@ static void execute_commands(struct command *commands,\n \tfree(head_name_to_free);\n \thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, NULL, NULL);\n \n+\tif (run_proc_receive &&\n+\t    run_proc_receive_hook(commands, push_options))\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tif (!cmd->error_string  && (cmd->run_proc_receive || use_atomic))\n+\t\t\t\tcmd->error_string = \"fail to run proc-receive hook\";\n+\n \tif (use_atomic)\n \t\texecute_commands_atomic(commands, si);\n \telse\n@@ -1899,12 +2186,23 @@ static void report(struct command *commands, const char *unpack_status)\n \tpacket_buf_write(&buf, \"unpack %s\\n\",\n \t\t\t unpack_status ? unpack_status : \"ok\");\n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!cmd->error_string)\n-\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n-\t\t\t\t\t cmd->ref_name);\n-\t\telse\n-\t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n-\t\t\t\t\t cmd->ref_name, cmd->error_string);\n+\t\tif (!cmd->error_string) {\n+\t\t\tif (cmd->extended_status)\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s%c%s\\n\",\n+\t\t\t\t\t\t cmd->ref_name, '\\0',\n+\t\t\t\t\t\t cmd->extended_status);\n+\t\t\telse\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n+\t\t\t\t\t\t cmd->ref_name);\n+\t\t} else {\n+\t\t\tif (cmd->extended_status)\n+\t\t\t\tpacket_buf_write(&buf, \"ng %s %s%c%s\\n\",\n+\t\t\t\t\t\t cmd->ref_name, cmd->error_string,\n+\t\t\t\t\t\t '\\0', cmd->extended_status);\n+\t\t\telse\n+\t\t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n+\t\t\t\t\t\t cmd->ref_name, cmd->error_string);\n+\t\t}\n \t}\n \tpacket_buf_flush(&buf);\n \ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nnew file mode 100644\nindex 0000000000..c9f5c5aae4\n--- /dev/null\n+++ b/t/helper/test-proc-receive.c\n@@ -0,0 +1,178 @@\n+#include \"cache.h\"\n+#include \"connect.h\"\n+#include \"parse-options.h\"\n+#include \"pkt-line.h\"\n+#include \"sigchain.h\"\n+#include \"string-list.h\"\n+#include \"test-tool.h\"\n+\n+static const char *proc_receive_usage[] = {\n+\t\"test-tool proc-receive [<options>...]\",\n+\tNULL\n+};\n+\n+static int version = 1;\n+static int verbose = 0;\n+static int no_push_options = 0;\n+static int use_atomic = 0;\n+static int use_push_options = 0;\n+static struct string_list returns = STRING_LIST_INIT_NODUP;\n+\n+struct command {\n+\tstruct command *next;\n+\tconst char *error_string;\n+\tunsigned int skip_update:1,\n+\t\t     did_not_exist:1;\n+\tint index;\n+\tstruct object_id old_oid;\n+\tstruct object_id new_oid;\n+\tchar ref_name[FLEX_ARRAY]; /* more */\n+};\n+\n+static void proc_receive_verison(struct packet_reader *reader) {\n+\tint server_version = 0;\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader->pktlen > 8 && starts_with(reader->line, \"version=\")) {\n+\t\t\tserver_version = atoi(reader->line+8);\n+\t\t\tlinelen = strlen(reader->line);\n+\t\t\tif (linelen < reader->pktlen) {\n+\t\t\t\tconst char *feature_list = reader->line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"atomic\"))\n+\t\t\t\t\tuse_atomic= 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tuse_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (server_version != 1)\n+\t\tdie(\"bad protocol version: %d\", server_version);\n+\n+\tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n+\t\t\t version, '\\0',\n+\t\t\t use_push_options && !no_push_options ? \"push-options\": \"\");\n+\tpacket_flush(1);\n+}\n+\n+static void proc_receive_read_commands(struct packet_reader *reader,\n+\t\t\t\t       struct command **commands)\n+{\n+\tstruct command **tail = commands;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tstruct command *cmd;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ')\n+\t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n+\t\t\t    reader->line);\n+\t\trefname = p;\n+\t\tFLEX_ALLOC_STR(cmd, ref_name, refname);\n+\t\toidcpy(&cmd->old_oid, &old_oid);\n+\t\toidcpy(&cmd->new_oid, &new_oid);\n+\n+\t\t*tail = cmd;\n+\t\ttail = &cmd->next;\n+\t}\n+}\n+\n+static void proc_receive_read_push_options(struct packet_reader *reader,\n+\t\t\t\t\t   struct string_list *options)\n+{\n+\n+\tif (no_push_options || !use_push_options)\n+\t       return;\n+\n+\twhile (1) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tstring_list_append(options, reader->line);\n+\t}\n+}\n+\n+int cmd__proc_receive(int argc, const char **argv)\n+{\n+\tstruct packet_reader reader;\n+\tstruct command *commands = NULL;\n+\tstruct string_list push_options = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"no-push-options\", &no_push_options,\n+\t\t\t \"disable push options\"),\n+\t\tOPT_STRING_LIST('r', \"return\", &returns, \"old/new/ref/status/msg\",\n+\t\t\t\t\"return of results\"),\n+\t\tOPT__VERBOSE(&verbose, \"be verbose\"),\n+\t\tOPT_INTEGER('V', \"version\", &version,\n+\t\t\t    \"use this protocol version number\"),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, \"test-tools\", options, proc_receive_usage, 0);\n+\tif (argc > 0)\n+\t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n+\n+\tpacket_reader_init(&reader, 0, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\tproc_receive_verison(&reader);\n+\tproc_receive_read_commands(&reader, &commands);\n+\tproc_receive_read_push_options(&reader, &push_options);\n+\n+\tif (verbose) {\n+\t\tstruct command *cmd;\n+\n+\t\tif (use_push_options || use_atomic)\n+\t\t\tfprintf(stderr, \"proc-receive:%s%s\\n\",\n+\t\t\t\tuse_atomic? \" atomic\": \"\",\n+\t\t\t\tuse_push_options ? \" push_options\": \"\");\n+\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tfprintf(stderr, \"proc-receive< %s %s %s\\n\",\n+\t\t\t\toid_to_hex(&cmd->old_oid),\n+\t\t\t\toid_to_hex(&cmd->new_oid),\n+\t\t\t\tcmd->ref_name);\n+\n+\t\tif (push_options.nr > 0)\n+\t\t\tfor_each_string_list_item(item, &push_options)\n+\t\t\t\tfprintf(stderr, \"proc-receive< %s\\n\", item->string);\n+\n+\t\tif (returns.nr)\n+\t\t\tfor_each_string_list_item(item, &returns)\n+\t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n+\t}\n+\n+\tif (returns.nr)\n+\t\tfor_each_string_list_item(item, &returns) {\n+\t\t\tchar *p;\n+\n+\t\t\tp = strstr(item->string, \"\\\\0\");\n+\t\t\tif (p) {\n+\t\t\t\t*p = '\\0';\n+\t\t\t\tp += 2;\n+\t\t\t\tpacket_write_fmt(1, \"%s%c%s\\n\", item->string, '\\0', p);\n+\t\t\t} else {\n+\t\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n+\t\t\t}\n+\t\t}\n+\tpacket_flush(1);\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 31eedcd241..f865433a61 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -44,6 +44,7 @@ static struct test_cmd cmds[] = {\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\n+\t{ \"proc-receive\", cmd__proc_receive},\n \t{ \"progress\", cmd__progress },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 4eb5e6609e..059008cf22 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -34,6 +34,7 @@ int cmd__parse_pathspec_file(int argc, const char** argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\n+int cmd__proc_receive(int argc, const char **argv);\n int cmd__progress(int argc, const char **argv);\n int cmd__reach(int argc, const char **argv);\n int cmd__read_cache(int argc, const char **argv);\ndiff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\nindex 4337b10c8c..1fbd24dbaf 100644\n--- a/t/t5411/common-functions.sh\n+++ b/t/t5411/common-functions.sh\n@@ -34,8 +34,11 @@ create_commits_in () {\n # Format the output of git-push, git-show-ref and other commands to make a\n # user-friendly and stable text.  We can easily prepare the expect text\n # without having to worry about future changes of the commit ID and spaces\n-# of the output.  We also replce single quotes with double quotes, because\n-# it is boring to prepare unquoted single quotes in expect text.\n+# of the output.  Single quotes are replaced with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect text.  We also\n+# remove some locale error messages, which break test if we turn on\n+# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n+# on plumbing commands.\n make_user_friendly_and_stable_output_common () {\n \tsed \\\n \t\t-e \"s/  *\\$//\" \\\n@@ -46,5 +49,6 @@ make_user_friendly_and_stable_output_common () {\n \t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n \t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n \t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n-\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\"\n+\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\" \\\n+\t\t-e \"/^error: / d\"\n }\ndiff --git a/t/t5411/common-test-cases.sh b/t/t5411/common-test-cases.sh\nindex 23655846e4..e850292e50 100644\n--- a/t/t5411/common-test-cases.sh\n+++ b/t/t5411/common-test-cases.sh\n@@ -41,3 +41,679 @@ test_expect_success \"normal git-push command\" '\n \tEOF\n \ttest_cmp expect actual\n '\n+\n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, fail to push special ref\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, fail all for atomic push\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\t$B:master \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] <COMMIT-B> -> master (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown version)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (unknown version)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\t# Check status report for git-push\n+\tsed -n \\\n+\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n+\t\t-e \"/^ / { s/   */ /g; p; }\" \\\n+\t\t<actual >actual-report &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual-report &&\n+\n+\t# Check error message from \"receive-pack\", but ignore unstable fatal error\n+\t# message (\"remote: fatal: the remote end hung up unexpectedly\") which\n+\t# is different from the remote HTTP server with different locale settings.\n+\tsed -n -e \"/^remote: error:/ { s/   */ /g; p; }\" \\\n+\t\t<actual >actual-error &&\n+\tcat >expect <<-EOF &&\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tEOF\n+\ttest_cmp expect actual-error &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (no report)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (no ref)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (no ref)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok\n+\tremote: error: proc-receive expected \"<status> <ref> ...\", got \"ok\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"xx refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (unknown status)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> xx refs/for/master/topic\n+\tremote: error: proc-receive reported bad status \"xx\" on ref \"refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng message)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic error msg\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unexpected ref)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: report on unexpected ref\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: error: proc-receive reported status on unexpected ref: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\n+\n+test_expect_success \"setup proc-receive hook (ok)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: failed to report status\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"proc-receive: not support push options\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n+\t\tactual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"proc-receive: push with options\" '\n+\tgit -C workbench push \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+test_expect_success \"setup proc-receive hook (ft)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ft refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(B)\n+test_expect_success \"proc-receive: fall throught, let receive-pack to execute\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: proc-receive> ft refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] <COMMIT-B> -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/for/master/topic\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             refs/for/master/topic(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\tgit -C \"$upstream\" update-ref -d refs/for/master/topic\n+'\n+\n+test_expect_success \"setup proc-receive hook (with extended status)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/a/b/c/topic\" \\\n+\t\t-r \"alt refs/for/next/topic refs/pull/123/head\" \\\n+\t\t-r \"alt refs/for/master/topic refs/pull/124/head old-oid=$B forced-update new-oid=$A\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report with extended status\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/for/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/a/b/c/topic\n+\tremote: proc-receive> alt refs/for/next/topic refs/pull/123/head\n+\tremote: proc-receive> alt refs/for/master/topic refs/pull/124/head old-oid=<COMMIT-B> forced-update new-oid=<COMMIT-A>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/for/a/b/c/topic\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple responses)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"alt refs/for/master/topic refs/changes/23/123/1\" \\\n+\t\t-r \"alt refs/for/master/topic refs/changes/24/124/2 old-oid=$A new-oid=$B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report multiple response\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> alt refs/for/master/topic refs/changes/23/123/1\n+\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/2 old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"alt refs/for/master/topic refs/for/master/topic old-oid=$A new-oid=$B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report update of mixed refs\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> alt refs/for/master/topic refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t * [new branch] HEAD -> bar\n+\t * [new branch] HEAD -> baz\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new branch] HEAD -> foo\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395405","messageId":"20200414123257.27449-5-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqq3697cax0.fsf@gitster.c.googlers.com","subject":"[PATCH v12 4/7] send-pack: extension for client-side status report","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-14T12:32:54Z","receivedAt":"2020-04-14T12:33:28Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThis is the client-side extension for status report of git-push.\n\nThe \"proc-receive\" hook may receive a command for a pseudo-reference\nwith a zero-old as its old-oid, while the result of the hook may point\nto an alternate reference and the reference may exist already with a\nnon-zero old-oid.  Git client may receive a report with extened status\nas the following example:\n\n    ok refs/for/master/topic\\0ref=refs/pull/123/head old-oid=...\n\nParse the key-value pairs in the extended status after the null\ncharacter of the report, and show the proper reference name, old-oid\nand new-oid to the user.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n remote.c                     | 28 +++++++++++++\n remote.h                     |  3 ++\n send-pack.c                  | 11 +++++-\n t/t5411/common-test-cases.sh |  9 +++--\n transport-helper.c           | 64 +++++++++++++++---------------\n transport.c                  | 76 +++++++++++++++++++++++++++++++++---\n 6 files changed, 149 insertions(+), 42 deletions(-)\n\ndiff --git a/remote.c b/remote.c\nindex c43196ec06..0b22d9e14c 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"config.h\"\n+#include \"connect.h\"\n #include \"remote.h\"\n #include \"refs.h\"\n #include \"refspec.h\"\n@@ -2311,3 +2312,30 @@ void apply_push_cas(struct push_cas_option *cas,\n \tfor (ref = remote_refs; ref; ref = ref->next)\n \t\tapply_cas(cas, remote, ref);\n }\n+\n+void update_ref_from_remote_status(struct ref *ref)\n+{\n+\tchar *val;\n+\tchar c;\n+\tint len;\n+\n+\tif (!ref->remote_status)\n+\t\treturn;\n+\n+\tval = (char *)parse_feature_value(ref->remote_status, \"old-oid\", &len);\n+\tif (val && len) {\n+\t\tc = *(val+len);\n+\t\t*(val+len) = '\\0';\n+\t\tget_oid_hex(val, &ref->old_oid);\n+\t\t*(val+len) = c;\n+\t}\n+\tval = (char *)parse_feature_value(ref->remote_status, \"new-oid\", &len);\n+\tif (val && len) {\n+\t\tc = *(val+len);\n+\t\t*(val+len) = '\\0';\n+\t\tget_oid_hex(val, &ref->new_oid);\n+\t\t*(val+len) = c;\n+\t}\n+\tif (parse_feature_request(ref->remote_status, \"forced-update\"))\n+\t\tref->forced_update = 1;\n+}\ndiff --git a/remote.h b/remote.h\nindex 11d8719b58..09cf7014e3 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -345,4 +345,7 @@ int parseopt_push_cas_option(const struct option *, const char *arg, int unset);\n int is_empty_cas(const struct push_cas_option *);\n void apply_push_cas(struct push_cas_option *, struct remote *, struct ref *);\n \n+/* Parse key-value pairs of remote_status and update the reference accordingly */\n+void update_ref_from_remote_status(struct ref *ref);\n+\n #endif\ndiff --git a/send-pack.c b/send-pack.c\nindex 0407841ae8..166e986aa6 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -159,6 +159,9 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \twhile (1) {\n \t\tconst char *refname;\n \t\tchar *msg;\n+\t\tchar *extended_status = NULL;\n+\t\tint len;\n+\n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n \t\t\tbreak;\n \t\tif (!starts_with(reader->line, \"ok \") && !starts_with(reader->line, \"ng \")) {\n@@ -167,10 +170,13 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \t\t\tbreak;\n \t\t}\n \n+\t\tlen = strlen(reader->line);\n \t\trefname = reader->line + 3;\n \t\tmsg = strchr(refname, ' ');\n \t\tif (msg)\n \t\t\t*msg++ = '\\0';\n+\t\tif (reader->pktlen > len)\n+\t\t\textended_status = (char *)reader->line + len + 1;\n \n \t\t/* first try searching at our hint, falling back to all refs */\n \t\tif (hint)\n@@ -194,7 +200,10 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \t\t\thint->status = REF_STATUS_REMOTE_REJECT;\n \t\t\tret = -1;\n \t\t}\n-\t\thint->remote_status = xstrdup_or_null(msg);\n+\t\tif (msg)\n+\t\t\thint->remote_status = xstrdup(msg);\n+\t\telse if (extended_status)\n+\t\t\thint->remote_status = xstrdup(extended_status);\n \t\t/* start our next search from the next ref */\n \t\thint = hint->next;\n \t}\ndiff --git a/t/t5411/common-test-cases.sh b/t/t5411/common-test-cases.sh\nindex e850292e50..397748dd6f 100644\n--- a/t/t5411/common-test-cases.sh\n+++ b/t/t5411/common-test-cases.sh\n@@ -605,9 +605,9 @@ test_expect_success \"proc-receive: report with extended status\" '\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \t * [new reference] HEAD -> refs/for/a/b/c/topic\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -645,7 +645,8 @@ test_expect_success \"proc-receive: report multiple response\" '\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/23/123/1\n+\t <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -704,7 +705,7 @@ test_expect_success \"proc-receive: report update of mixed refs\" '\n \t * [new branch] HEAD -> baz\n \t * [new reference] HEAD -> refs/for/next/topic\n \t * [new branch] HEAD -> foo\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 20a7185ec4..cec3495d59 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -747,37 +747,39 @@ static int push_update_ref_status(struct strbuf *buf,\n \t\t\tmsg = xstrdup(msg);\n \t\tstrbuf_release(&msg_buf);\n \n-\t\tif (!strcmp(msg, \"no match\")) {\n-\t\t\tstatus = REF_STATUS_NONE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"up to date\")) {\n-\t\t\tstatus = REF_STATUS_UPTODATE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"non-fast forward\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_NONFASTFORWARD;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"already exists\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_ALREADY_EXISTS;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"fetch first\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_FETCH_FIRST;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"needs force\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_NEEDS_FORCE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"stale info\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_STALE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"forced update\")) {\n-\t\t\tforced = 1;\n-\t\t\tFREE_AND_NULL(msg);\n+\t\tif (status != REF_STATUS_OK) {\n+\t\t\tif (!strcmp(msg, \"no match\")) {\n+\t\t\t\tstatus = REF_STATUS_NONE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"up to date\")) {\n+\t\t\t\tstatus = REF_STATUS_UPTODATE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"non-fast forward\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_NONFASTFORWARD;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"already exists\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_ALREADY_EXISTS;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"fetch first\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_FETCH_FIRST;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"needs force\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_NEEDS_FORCE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"stale info\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_STALE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"forced update\")) {\n+\t\t\t\tforced = 1;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n \t\t}\n \t}\n \ndiff --git a/transport.c b/transport.c\nindex 272c0f4046..dbff7c50e5 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -463,11 +463,23 @@ static void print_ref_status(char flag, const char *summary,\n \t\t\t     struct ref *to, struct ref *from, const char *msg,\n \t\t\t     int porcelain, int summary_width)\n {\n+\tchar *to_name = NULL;\n+\tconst char *val;\n+\tint len;\n+\n+\tif (to && to->remote_status) {\n+\t\tval = parse_feature_value(to->remote_status, \"ref\", &len);\n+\t\tif (val && len)\n+\t\tto_name = xmemdupz(val, len);\n+\t}\n+\n \tif (porcelain) {\n \t\tif (from)\n-\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name,\n+\t\t\t\tto_name ? to_name : to->name);\n \t\telse\n-\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag,\n+\t\t\t\tto_name ? to_name : to->name);\n \t\tif (msg)\n \t\t\tfprintf(stdout, \"%s (%s)\\n\", summary, msg);\n \t\telse\n@@ -481,9 +493,11 @@ static void print_ref_status(char flag, const char *summary,\n \t\tfprintf(stderr, \" %s%c %-*s%s \", red, flag, summary_width,\n \t\t\tsummary, reset);\n \t\tif (from)\n-\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(to->name));\n+\t\t\tfprintf(stderr, \"%s -> %s\",\n+\t\t\t\tprettify_refname(from->name),\n+\t\t\t\tprettify_refname(to_name ? to_name : to->name));\n \t\telse\n-\t\t\tfputs(prettify_refname(to->name), stderr);\n+\t\t\tfputs(prettify_refname(to_name ? to_name : to->name), stderr);\n \t\tif (msg) {\n \t\t\tfputs(\" (\", stderr);\n \t\t\tfputs(msg, stderr);\n@@ -491,6 +505,7 @@ static void print_ref_status(char flag, const char *summary,\n \t\t}\n \t\tfputc('\\n', stderr);\n \t}\n+\tfree(to_name);\n }\n \n static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_width)\n@@ -531,8 +546,8 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t}\n }\n \n-static int print_one_push_status(struct ref *ref, const char *dest, int count,\n-\t\t\t\t int porcelain, int summary_width)\n+static int _print_one_push_status(struct ref *ref, const char *dest, int count,\n+\t\t\t\t  int porcelain, int summary_width)\n {\n \tif (!count) {\n \t\tchar *url = transport_anonymize_url(dest);\n@@ -602,6 +617,55 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count,\n \treturn 1;\n }\n \n+static int print_one_push_status(struct ref *ref, const char *dest, int count,\n+\t\t\t\t int porcelain, int summary_width)\n+{\n+\tchar *head;\n+\tchar *begin;\n+\tint n = 0;\n+\n+\tif (!ref->remote_status)\n+\t\treturn _print_one_push_status(ref, dest, count,\n+\t\t\t\t\t      porcelain, summary_width);\n+\n+\thead = ref->remote_status;\n+\tbegin = strstr(head, \"ref=\");\n+\tif (!begin)\n+\t\tbegin = ref->remote_status;\n+\tfor (;;) {\n+\t\tchar *end;\n+\t\tstruct object_id old_oid;\n+\t\tstruct object_id new_oid;\n+\t\tint forced_update;\n+\n+\t\tend = strstr(begin + 4, \"ref=\");\n+\t\tif (end)\n+\t\t\t*(end-1) = '\\0';\n+\n+\t\toidcpy(&old_oid, &ref->old_oid);\n+\t\toidcpy(&new_oid, &ref->new_oid);\n+\t\tforced_update = ref->forced_update;\n+\n+\t\tref->remote_status = begin;\n+\t\tupdate_ref_from_remote_status(ref);\n+\t\t_print_one_push_status(ref, dest, count + n++,\n+\t\t\t\t       porcelain, summary_width);\n+\n+\t\toidcpy(&ref->old_oid, &old_oid);\n+\t\toidcpy(&ref->new_oid, &new_oid);\n+\t\tref->forced_update = forced_update;\n+\n+\t\tif (end) {\n+\t\t\tbegin = end;\n+\t\t\t*(end-1) = ' ';\n+\t\t} else {\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\tref->remote_status = head;\n+\treturn n;\n+}\n+\n static int measure_abbrev(const struct object_id *oid, int sofar)\n {\n \tchar hex[GIT_MAX_HEXSZ + 1];\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395407","messageId":"20200414123257.27449-6-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqq3697cax0.fsf@gitster.c.googlers.com","subject":"[PATCH v12 5/7] refs.c: refactor to reuse ref_is_hidden()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-14T12:32:55Z","receivedAt":"2020-04-14T12:33:30Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd new function `ref_is_matched()` to reuse `ref_is_hidden()`. Will use\nthis function for `receive-pack` to check commands with specific\nprefixes.\n\nTest case t5512 covered this change.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n refs.c | 13 +++++++++----\n refs.h |  1 +\n 2 files changed, 10 insertions(+), 4 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 1ab0bb54d3..a20f56a463 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1388,14 +1388,14 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti\n \treturn 0;\n }\n \n-int ref_is_hidden(const char *refname, const char *refname_full)\n+int ref_matches(struct string_list *match_refs, const char *refname, const char *refname_full)\n {\n \tint i;\n \n-\tif (!hide_refs)\n+\tif (!match_refs)\n \t\treturn 0;\n-\tfor (i = hide_refs->nr - 1; i >= 0; i--) {\n-\t\tconst char *match = hide_refs->items[i].string;\n+\tfor (i = match_refs->nr - 1; i >= 0; i--) {\n+\t\tconst char *match = match_refs->items[i].string;\n \t\tconst char *subject;\n \t\tint neg = 0;\n \t\tconst char *p;\n@@ -1421,6 +1421,11 @@ int ref_is_hidden(const char *refname, const char *refname_full)\n \treturn 0;\n }\n \n+int ref_is_hidden(const char *refname, const char *refname_full)\n+{\n+\treturn ref_matches(hide_refs, refname, refname_full);\n+}\n+\n const char *find_descendant_ref(const char *dirname,\n \t\t\t\tconst struct string_list *extras,\n \t\t\t\tconst struct string_list *skip)\ndiff --git a/refs.h b/refs.h\nindex 545029c6d8..d02c72d3ad 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -739,6 +739,7 @@ int parse_hide_refs_config(const char *var, const char *value, const char *);\n  * parameter always points to the full ref name.\n  */\n int ref_is_hidden(const char *, const char *);\n+int ref_matches(struct string_list *, const char *, const char *);\n \n enum ref_type {\n \tREF_TYPE_PER_WORKTREE,\t  /* refs inside refs/ but not shared       */\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395408","messageId":"20200414123257.27449-8-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqq3697cax0.fsf@gitster.c.googlers.com","subject":"[PATCH v12 7/7] doc: add documentation for the proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-14T12:32:57Z","receivedAt":"2020-04-14T12:33:35Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd documentation for the new \"proc-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/githooks.txt | 58 ++++++++++++++++++++++++++++++++++++++\n 1 file changed, 58 insertions(+)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 3dccab5375..a0ea829435 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -333,6 +333,64 @@ The default 'update' hook, when enabled--and with\n `hooks.allowunannotated` config option unset or set to false--prevents\n unannotated tags to be pushed.\n \n+proc-receive\n+~~~~~~~~~~~~\n+\n+This hook is invoked by linkgit:git-receive-pack[1].  If the server has\n+set the multi-valued config variable `receive.procReceiveRefs`, and the\n+commands sent to 'receive-pack' have matching reference names, these\n+commands will be executed by this hook, instead of by the internal\n+`execute_commands()` function.  This hook is responsible for updating\n+the relevant references and reporting the results back to 'receive-pack'.\n+\n+This hook executes once for the receive operation.  It takes no\n+arguments, but uses a pkt-line format protocol to communicate with\n+'receive-pack' to read commands, push-options and send results.  In the\n+following example for the protocol, the letter 'S' stands for\n+'receive-pack' and the letter 'H' stands for this hook.\n+\n+    # Version and features negotiation.\n+    S: PKT-LINE(version=1\\0push-options atomic...)\n+    S: flush-pkt\n+    H: PKT-LINE(version=1\\0push-options...)\n+    H: flush-pkt\n+\n+    # Send commands from server to the hook.\n+    S: PKT-LINE(<old-oid> <new-oid> <ref>)\n+    S: ... ...\n+    S: flush-pkt\n+    # Send push-options only if the 'push-options' feature is enabled.\n+    S: PKT-LINE(push-option)\n+    S: ... ...\n+    S: flush-pkt\n+\n+    # Receive result from the hook.\n+    # OK, run this command successfully.\n+    H: PKT-LINE(ok <ref>)\n+    # NO, I reject it.\n+    H: PKT-LINE(ng <ref> <reason>)\n+    # Fall through, let 'receive-pack' to execute it.\n+    H: PKT-LINE(ft <ref>)\n+    # OK, but has an alternate reference.  The alternate reference name\n+    # is given in the third parameter, and other status can be given in\n+    # key-value pairs.\n+    H: PKT-LINE(alt <ref> <alt-ref> old-oid=<old-oid> new-oid=<new-oid>\n+                forced-update)\n+    H: ... ...\n+    H: flush-pkt\n+\n+Each command for the 'proc-receive' hook may point to a pseudo-reference\n+and always has a zero-old as its old-oid, while the 'proc-receive' hook\n+may update an alternate reference and the alternate reference may exist\n+already with a non-zero old-oid.  For this case, this hook may return\n+different OID and different reference name as extended status of the\n+report line.\n+\n+The report of the commands of this hook should have the same order as\n+the input.  The exit status of the 'proc-receive' hook only determines\n+the success or failure of the group of commands sent to it, unless\n+atomic push is in use.\n+\n [[post-receive]]\n post-receive\n ~~~~~~~~~~~~\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395409","messageId":"20200414123257.27449-7-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqq3697cax0.fsf@gitster.c.googlers.com","subject":"[PATCH v12 6/7] receive-pack: new config receive.procReceiveRefs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-14T12:32:56Z","receivedAt":"2020-04-14T12:33:37Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd a new multi-valued config variable \"receive.procReceiveRefs\"\nfor `receive-pack` command, like the follows:\n\n    git config --system --add receive.procReceiveRefs refs/for\n    git config --system --add receive.procReceiveRefs refs/drafts\n\nIf the specific prefix strings match the reference names of the commands\nwhich are sent from git client to `receive-pack`, these commands will be\nexecuted by an external hook (named \"proc-receive\"), instead of the\ninternal `execute_commands` function.\n\nFor example, if it is set to \"refs/for\", pushing to a reference such as\n\"refs/for/master\" will not create or update reference \"refs/for/master\",\nbut may create or update a pull request directly by running the hook\n\"proc-receive\".\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/config/receive.txt | 14 +++++\n builtin/receive-pack.c           | 43 ++++++++++++---\n t/t5411/common-test-cases.sh     | 94 +++++++++++++++++++++++++++++---\n 3 files changed, 136 insertions(+), 15 deletions(-)\n\ndiff --git a/Documentation/config/receive.txt b/Documentation/config/receive.txt\nindex 65f78aac37..e7b967feeb 100644\n--- a/Documentation/config/receive.txt\n+++ b/Documentation/config/receive.txt\n@@ -114,6 +114,20 @@ receive.hideRefs::\n \tAn attempt to update or delete a hidden ref by `git push` is\n \trejected.\n \n+receive.procReceiveRefs::\n+\tThis is a multi-valued variable that defines reference prefixes\n+\tto match the commands in `receive-pack`.  Commands matching the\n+\tprefixes will be executed by an external hook \"proc-receive\",\n+\tinstead of the internal `execute_commands` function.  If this\n+\tvariable is not defined, the \"proc-receive\" hook will never be\n+\tused, and all commands will be executed by the internal\n+\t`execute_commands` function.\n+\n+\tFor example, if this variable is set to \"refs/for\", pushing to\n+\treference such as \"refs/for/master\" will not create or update a\n+\treference named \"refs/for/master\", but may create or update a\n+\tpull request directly by running the hook \"proc-receive\".\n+\n receive.updateServerInfo::\n \tIf set to true, git-receive-pack will run git-update-server-info\n \tafter receiving data from git-push and updating refs.\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex fbbb01135e..40409b6dae 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -77,6 +77,7 @@ static struct object_id push_cert_oid;\n static struct signature_check sigcheck;\n static const char *push_cert_nonce;\n static const char *cert_nonce_seed;\n+static struct string_list proc_receive_refs;\n \n static const char *NONCE_UNSOLICITED = \"UNSOLICITED\";\n static const char *NONCE_BAD = \"BAD\";\n@@ -229,6 +230,20 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (strcmp(var, \"receive.procreceiverefs\") == 0) {\n+\t\tchar *prefix;\n+\t\tint len;\n+\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tprefix = xstrdup(value);\n+\t\tlen = strlen(prefix);\n+\t\twhile (len && prefix[len - 1] == '/')\n+\t\t\tprefix[--len] = '\\0';\n+\t\tstring_list_append(&proc_receive_refs, prefix);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -1767,15 +1782,26 @@ static void execute_commands(struct command *commands,\n \t * Try to find commands that have special prefix in their reference names,\n \t * and mark them to run an external \"proc-receive\" hook later.\n \t */\n-\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n-\t\t\tcontinue;\n+\tif (proc_receive_refs.nr > 0) {\n+\t\tstruct strbuf refname_full = STRBUF_INIT;\n+\t\tsize_t prefix_len;\n+\n+\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n+\t\tprefix_len = refname_full.len;\n \n-\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n-\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n-\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n-\t\t\trun_proc_receive = 1;\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tif (!should_process_cmd(cmd))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_setlen(&refname_full, prefix_len);\n+\t\t\tstrbuf_addstr(&refname_full, cmd->ref_name);\n+\t\t\tif (ref_matches(&proc_receive_refs, cmd->ref_name, refname_full.buf)) {\n+\t\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\t\trun_proc_receive = 1;\n+\t\t\t}\n \t\t}\n+\n+\t\tstrbuf_release(&refname_full);\n \t}\n \n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n@@ -2240,6 +2266,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n+\tstring_list_init(&proc_receive_refs, 0);\n+\n \tpacket_trace_identity(\"receive-pack\");\n \n \targc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);\n@@ -2355,5 +2383,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \toid_array_clear(&shallow);\n \toid_array_clear(&ref);\n \tfree((void *)push_cert_nonce);\n+\tstring_list_clear(&proc_receive_refs, 0);\n \treturn 0;\n }\ndiff --git a/t/t5411/common-test-cases.sh b/t/t5411/common-test-cases.sh\nindex 397748dd6f..aa6319cf4d 100644\n--- a/t/t5411/common-test-cases.sh\n+++ b/t/t5411/common-test-cases.sh\n@@ -53,6 +53,14 @@ test_expect_success \"cleanup\" '\n \t)\n '\n \n+test_expect_success \"add two receive.procReceiveRefs settings\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit config --add receive.procReceiveRefs refs/for &&\n+\t\tgit config --add receive.procReceiveRefs refs/review/\n+\t)\n+'\n+\n # Refs of upstream : master(A)\n # Refs of workbench: master(A)  tags/v123\n # git push         :                       next(A)  refs/for/master/topic(A)\n@@ -572,7 +580,7 @@ test_expect_success \"setup proc-receive hook (with extended status)\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n \ttest-tool proc-receive -v \\\n-\t\t-r \"ok refs/for/a/b/c/topic\" \\\n+\t\t-r \"ok refs/review/a/b/c/topic\" \\\n \t\t-r \"alt refs/for/next/topic refs/pull/123/head\" \\\n \t\t-r \"alt refs/for/master/topic refs/pull/124/head old-oid=$B forced-update new-oid=$A\"\n \tEOF\n@@ -580,33 +588,33 @@ test_expect_success \"setup proc-receive hook (with extended status)\" '\n \n # Refs of upstream : master(A)\n # Refs of workbench: master(A)  tags/v123\n-# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+# git push         :                       refs/for/next/topic(A)  refs/review/a/b/c/topic(A)  refs/for/master/topic(A)\n test_expect_success \"proc-receive: report with extended status\" '\n \tgit -C workbench push origin \\\n \t\tHEAD:refs/for/next/topic \\\n-\t\tHEAD:refs/for/a/b/c/topic \\\n+\t\tHEAD:refs/review/a/b/c/topic \\\n \t\tHEAD:refs/for/master/topic \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n-\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tremote: # proc-receive hook\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n-\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n-\tremote: proc-receive> ok refs/for/a/b/c/topic\n+\tremote: proc-receive> ok refs/review/a/b/c/topic\n \tremote: proc-receive> alt refs/for/next/topic refs/pull/123/head\n \tremote: proc-receive> alt refs/for/master/topic refs/pull/124/head old-oid=<COMMIT-B> forced-update new-oid=<COMMIT-A>\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/pull/123/head\n-\t * [new reference] HEAD -> refs/for/a/b/c/topic\n+\t * [new reference] HEAD -> refs/review/a/b/c/topic\n \t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n@@ -718,3 +726,73 @@ test_expect_success \"proc-receive: report update of mixed refs\" '\n \tEOF\n \ttest_cmp expect actual\n '\n+\n+test_expect_success \"config add receive.procReceiveRefs = refs\" '\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs\n+'\n+\n+test_expect_success \"setup proc-receive hook\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ft refs/heads/master\" \\\n+\t\t-r \"ft refs/heads/foo\" \\\n+\t\t-r \"ft refs/heads/bar\" \\\n+\t\t-r \"alt refs/for/master/topic refs/pull/123/head old-oid=$A new-oid=$B \" \\\n+\t\t-r \"alt refs/for/next/topic refs/pull/124/head old-oid=$B new-oid=$A forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"proc-receive: process all refs\" '\n+\tgit -C workbench push -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> ft refs/heads/master\n+\tremote: proc-receive> ft refs/heads/foo\n+\tremote: proc-receive> ft refs/heads/bar\n+\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n+\tremote: proc-receive> alt refs/for/next/topic refs/pull/124/head old-oid=<COMMIT-B> new-oid=<COMMIT-A> forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> bar\n+\t - [deleted] foo\n+\t + <OID-B>...<OID-A> HEAD -> master (forced update)\n+\t <OID-A>..<OID-B> HEAD -> refs/pull/123/head\n+\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395507","messageId":"xmqqo8rs7ntb.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200414123257.27449-4-worldhello.net@gmail.com","subject":"Re: [PATCH v12 3/7] receive-pack: add new proc-receive hook","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-04-15T15:48:00Z","receivedAt":"2020-04-15T15:48:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\n> index 2cc18bbffd..fbbb01135e 100644\n> --- a/builtin/receive-pack.c\n> +++ b/builtin/receive-pack.c\n> @@ -13,6 +13,7 @@\n>  #include \"remote.h\"\n>  #include \"connect.h\"\n>  #include \"string-list.h\"\n> +#include \"string.h\"\n>  #include \"sha1-array.h\"\n>  #include \"connected.h\"\n>  #include \"argv-array.h\"\n\nWhat are you using from the system <string.h>, that is not covered\nby existing inclusion of \"git-compat-util.h\"?  \n\nDon't include any system header directly in our *.c code (unless it\nis a platform-specific compat/ thing).\n"},{"id":"395509","messageId":"CANYiYbE84Jj46m0ZTfAepc7t4v4KcqU9R99WGY9b7cdTXBfw_g@mail.gmail.com","threadId":"52936","inReplyTo":"xmqqo8rs7ntb.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v12 3/7] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-15T15:55:25Z","receivedAt":"2020-04-15T15:55:44Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2020年4月15日周三 下午11:48写道：\n>\n> Jiang Xin <worldhello.net@gmail.com> writes:\n>\n> > diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\n> > index 2cc18bbffd..fbbb01135e 100644\n> > --- a/builtin/receive-pack.c\n> > +++ b/builtin/receive-pack.c\n> > @@ -13,6 +13,7 @@\n> >  #include \"remote.h\"\n> >  #include \"connect.h\"\n> >  #include \"string-list.h\"\n> > +#include \"string.h\"\n> >  #include \"sha1-array.h\"\n> >  #include \"connected.h\"\n> >  #include \"argv-array.h\"\n>\n> What are you using from the system <string.h>, that is not covered\n> by existing inclusion of \"git-compat-util.h\"?\n>\n> Don't include any system header directly in our *.c code (unless it\n> is a platform-specific compat/ thing).\n\nWill correct it.\n\nWhat I am working on for reroll v13:\n\n* Add new commit \"receive-pack: feed extended_status to post-receive“\n\n   When commands are fed to the \"post-receive\" hook, `extended_status` will\n   be parsed and the real old-oid, new-oid, reference name will feed to\n   the \"post-receive\" hook.\n\n* Add test cases for \"git push --porcelain\".\n\n   I found some changes not covered by test.  So I decide add some\n   test cases for \"git push --porcelain\".  I will split the test cases into\n   multiple files inside \"t5411/\" like:\n\n       t5411/test-0000-normal-push.sh\n       t5411/test-0001-normal-push--porcelain.sh\n\n* Other minor fixes.\n"},{"id":"395526","messageId":"xmqqzhbc61jv.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"CANYiYbE84Jj46m0ZTfAepc7t4v4KcqU9R99WGY9b7cdTXBfw_g@mail.gmail.com","subject":"Re: [PATCH v12 3/7] receive-pack: add new proc-receive hook","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-04-15T18:34:12Z","receivedAt":"2020-04-15T19:17:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> Will correct it.\n>\n> What I am working on for reroll v13:\n>\n> * Add new commit \"receive-pack: feed extended_status to post-receive“\n>\n>    When commands are fed to the \"post-receive\" hook, `extended_status` will\n>    be parsed and the real old-oid, new-oid, reference name will feed to\n>    the \"post-receive\" hook.\n>\n> * Add test cases for \"git push --porcelain\".\n>\n>    I found some changes not covered by test.  So I decide add some\n>    test cases for \"git push --porcelain\".  I will split the test cases into\n>    multiple files inside \"t5411/\" like:\n>\n>        t5411/test-0000-normal-push.sh\n>        t5411/test-0001-normal-push--porcelain.sh\n>\n> * Other minor fixes.\n\nThanks for a heads-up, but given that nobody seems to be reading\nyour patches and commenting on them, you might want to slow down a\nbit.\n"},{"id":"395534","messageId":"xmqqh7xk5vwc.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200414123257.27449-5-worldhello.net@gmail.com","subject":"Re: [PATCH v12 4/7] send-pack: extension for client-side status report","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-04-15T20:36:19Z","receivedAt":"2020-04-15T20:36:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> +\t\tif (msg)\n> +\t\t\thint->remote_status = xstrdup(msg);\n> +\t\telse if (extended_status)\n> +\t\t\thint->remote_status = xstrdup(extended_status);\n\ncontrib/coccinelle/xstrdup_or_null.cocci suggests to rewrite the\nabove like so:\n\n\t\tif (msg)\n\t\t\thint->remote_status = xstrdup(msg);\n\t\telse\n\t\t\thint->remote_status = xstrdup_or_null(extended_status);\n\nI'll queue a fix-up patch to keep 'pu' passing the test suite for now.\n"},{"id":"395600","messageId":"20200416162415.5751-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200329143329.13846-1-worldhello.net@gmail.com","subject":"[PATCH v3 1/5] send-pack: fix inconsistent porcelain output","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-16T16:24:11Z","receivedAt":"2020-04-16T16:24:27Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThe porcelain output of a failed `git-push` command is inconsistent for\ndifferent protocols.  For example, the following `git-push` command\nfailed because of the failure of `pre-receive` hook.\n\n    git push --porcelain origin HEAD:refs/heads/master\n\nFor SSH protocol, the porcelain output does not end with a \"Done\"\nmessage:\n\n\tTo <URL/of/upstream.git>\n\t!  HEAD:refs/heads/master  [remote rejected] (pre-receive hook declined)\n\nWhile for HTTP protocol, the porcelain output does end with a \"Done\"\nmessage;\n\n\tTo <URL/of/upstream.git>\n\t!  HEAD:refs/heads/master  [remote rejected] (pre-receive hook declined)\n\tDone\n\nThe following code at the end of function `send_pack()` indicates that\n`send_pack()` should not return an error if some references are rejected\nin porcelain mode.\n\n    int send_pack(...)\n        ... ...\n\n        if (args->porcelain)\n            return 0;\n\n        for (ref = remote_refs; ref; ref = ref->next) {\n            switch (ref->status) {\n            case REF_STATUS_NONE:\n            case REF_STATUS_UPTODATE:\n            case REF_STATUS_OK:\n                break;\n            default:\n                return -1;\n            }\n        }\n        return 0;\n    }\n\nSo if atomic push failed, must check the porcelain mode before return\nan error.  And `receive_status()` should not return an error for a\nfailed updated reference, because `send_pack()` will check them instead.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n send-pack.c               |   7 +-\n t/t5548-push-porcelain.sh | 301 ++++++++++++++++++++++++++++++++++++++\n transport.c               |  10 +-\n 3 files changed, 313 insertions(+), 5 deletions(-)\n create mode 100755 t/t5548-push-porcelain.sh\n\ndiff --git a/send-pack.c b/send-pack.c\nindex 0407841ae8..1835cd5582 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -190,10 +190,8 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \n \t\tif (reader->line[0] == 'o' && reader->line[1] == 'k')\n \t\t\thint->status = REF_STATUS_OK;\n-\t\telse {\n+\t\telse\n \t\t\thint->status = REF_STATUS_REMOTE_REJECT;\n-\t\t\tret = -1;\n-\t\t}\n \t\thint->remote_status = xstrdup_or_null(msg);\n \t\t/* start our next search from the next ref */\n \t\thint = hint->next;\n@@ -489,7 +487,8 @@ int send_pack(struct send_pack_args *args,\n \t\t\tif (use_atomic) {\n \t\t\t\tstrbuf_release(&req_buf);\n \t\t\t\tstrbuf_release(&cap_buf);\n-\t\t\t\treturn atomic_push_failure(args, remote_refs, ref);\n+\t\t\t\tatomic_push_failure(args, remote_refs, ref);\n+\t\t\t\treturn args->porcelain ? 0 : -1;\n \t\t\t}\n \t\t\t/* else fallthrough */\n \t\tdefault:\ndiff --git a/t/t5548-push-porcelain.sh b/t/t5548-push-porcelain.sh\nnew file mode 100755\nindex 0000000000..8d487b2a7a\n--- /dev/null\n+++ b/t/t5548-push-porcelain.sh\n@@ -0,0 +1,301 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+test_description='Test git push porcelain output'\n+\n+. ./test-lib.sh\n+\n+# Create commits in <repo> and assign each commit's oid to shell variables\n+# given in the arguments (A, B, and C). E.g.:\n+#\n+#     create_commits_in <repo> A B C\n+#\n+# NOTE: Never calling this function from a subshell since variable\n+# assignments will disappear when subshell exits.\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{} --)\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  We can easily prepare the expect text\n+# without having to worry about future changes of the commit ID and spaces\n+# of the output.\n+make_user_friendly_and_stable_output () {\n+\tsed \\\n+\t\t-e \"s/  *\\$//\" \\\n+\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/\t/    /g\" \\\n+\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n+\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n+\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n+\t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n+\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\" \\\n+\t\t-e \"s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#\"\n+}\n+\n+run_git_push_porcelain_output_test() {\n+\tcase $1 in\n+\thttp)\n+\t\tPROTOCOL=\"HTTP protocol\"\n+\t\tURL_PREFIX=\"http://.*\"\n+\t\t;;\n+\tfile)\n+\t\tPROTOCOL=\"builtin protocol\"\n+\t\tURL_PREFIX=\"\\.\\.\"\n+\t\t;;\n+\tesac\n+\n+\t# Refs of upstream : master(B)  foo(A)  bar(A)  baz(A)\n+\t# Refs of workbench: master(A)                  baz(A)  next(A)\n+\t# git-push         : master(A)  NULL    (B)     baz(A)  next(A)\n+\ttest_expect_success \"porcelain output of successful git-push ($PROTOCOL)\" '\n+\t\t(\n+\t\t\tcd workbench &&\n+\t\t\tgit update-ref refs/heads/master $A &&\n+\t\t\tgit update-ref refs/heads/baz $A &&\n+\t\t\tgit update-ref refs/heads/next $A &&\n+\t\t\tgit push --porcelain --force origin \\\n+\t\t\t\tmaster \\\n+\t\t\t\t:refs/heads/foo \\\n+\t\t\t\t$B:bar \\\n+\t\t\t\tbaz \\\n+\t\t\t\tnext\n+\t\t) >out &&\n+\t\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\tTo <URL/of/upstream.git>\n+\t\t=    refs/heads/baz:refs/heads/baz    [up to date]\n+\t\t     <COMMIT-B>:refs/heads/bar    <OID-A>..<OID-B>\n+\t\t-    :refs/heads/foo    [deleted]\n+\t\t+    refs/heads/master:refs/heads/master    <OID-B>...<OID-A> (forced update)\n+\t\t*    refs/heads/next:refs/heads/next    [new branch]\n+\t\tDone\n+\t\tEOF\n+\t\ttest_cmp expect actual &&\n+\n+\t\tgit -C \"$upstream\" show-ref >out &&\n+\t\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\t<COMMIT-B> refs/heads/bar\n+\t\t<COMMIT-A> refs/heads/baz\n+\t\t<COMMIT-A> refs/heads/master\n+\t\t<COMMIT-A> refs/heads/next\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\t# Refs of upstream : master(A)  bar(B)  baz(A)  next(A)\n+\t# Refs of workbench: master(B)  bar(A)  baz(A)  next(A)\n+\t# git-push         : master(B)  bar(A)  NULL    next(A)\n+\ttest_expect_success \"atomic push failed ($PROTOCOL)\" '\n+\t\t(\n+\t\t\tcd workbench &&\n+\t\t\tgit update-ref refs/heads/master $B &&\n+\t\t\tgit update-ref refs/heads/bar $A &&\n+\t\t\ttest_must_fail git push --atomic --porcelain origin \\\n+\t\t\t\tmaster \\\n+\t\t\t\tbar \\\n+\t\t\t\t:baz \\\n+\t\t\t\tnext\n+\t\t) >out &&\n+\t\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\tTo <URL/of/upstream.git>\n+\t\t!    refs/heads/bar:refs/heads/bar    [rejected] (non-fast-forward)\n+\t\t!    (delete):refs/heads/baz    [rejected] (atomic push failed)\n+\t\t!    refs/heads/master:refs/heads/master    [rejected] (atomic push failed)\n+\t\t!    refs/heads/next:refs/heads/next    [rejected] (atomic push failed)\n+\t\tDone\n+\t\tEOF\n+\t\ttest_cmp expect actual &&\n+\n+\t\tgit -C \"$upstream\" show-ref >out &&\n+\t\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\t<COMMIT-B> refs/heads/bar\n+\t\t<COMMIT-A> refs/heads/baz\n+\t\t<COMMIT-A> refs/heads/master\n+\t\t<COMMIT-A> refs/heads/next\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"prepare pre-receive hook ($PROTOCOL)\" '\n+\t\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n+\t\texit 1\n+\t\tEOF\n+\t'\n+\n+\t# Refs of upstream : master(A)  bar(B)  baz(A)  next(A)\n+\t# Refs of workbench: master(B)  bar(A)  baz(A)  next(A)\n+\t# git-push         : master(B)  bar(A)  NULL    next(A)\n+\ttest_expect_success \"pre-receive hook declined ($PROTOCOL)\" '\n+\t\t(\n+\t\t\tcd workbench &&\n+\t\t\tgit update-ref refs/heads/master $B &&\n+\t\t\tgit update-ref refs/heads/bar $A &&\n+\t\t\ttest_must_fail git push --porcelain --force origin \\\n+\t\t\t\tmaster \\\n+\t\t\t\tbar \\\n+\t\t\t\t:baz \\\n+\t\t\t\tnext\n+\t\t) >out &&\n+\t\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\tTo <URL/of/upstream.git>\n+\t\t=    refs/heads/next:refs/heads/next    [up to date]\n+\t\t!    refs/heads/bar:refs/heads/bar    [remote rejected] (pre-receive hook declined)\n+\t\t!    :refs/heads/baz    [remote rejected] (pre-receive hook declined)\n+\t\t!    refs/heads/master:refs/heads/master    [remote rejected] (pre-receive hook declined)\n+\t\tDone\n+\t\tEOF\n+\t\ttest_cmp expect actual &&\n+\n+\t\tgit -C \"$upstream\" show-ref >out &&\n+\t\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\t<COMMIT-B> refs/heads/bar\n+\t\t<COMMIT-A> refs/heads/baz\n+\t\t<COMMIT-A> refs/heads/master\n+\t\t<COMMIT-A> refs/heads/next\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"remove pre-receive hook ($PROTOCOL)\" '\n+\t\trm \"$upstream/hooks/pre-receive\"\n+\t'\n+\n+\t# Refs of upstream : master(A)  bar(B)  baz(A)  next(A)\n+\t# Refs of workbench: master(B)  bar(A)  baz(A)  next(A)\n+\t# git-push         : master(B)  bar(A)  NULL    next(A)\n+\ttest_expect_success \"non-fastforward push ($PROTOCOL)\" '\n+\t\t(\n+\t\t\tcd workbench &&\n+\t\t\ttest_must_fail git push --porcelain origin \\\n+\t\t\t\tmaster \\\n+\t\t\t\tbar \\\n+\t\t\t\t:baz \\\n+\t\t\t\tnext\n+\t\t) >out &&\n+\t\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\tTo <URL/of/upstream.git>\n+\t\t=    refs/heads/next:refs/heads/next    [up to date]\n+\t\t-    :refs/heads/baz    [deleted]\n+\t\t     refs/heads/master:refs/heads/master    <OID-A>..<OID-B>\n+\t\t!    refs/heads/bar:refs/heads/bar    [rejected] (non-fast-forward)\n+\t\tDone\n+\t\tEOF\n+\t\ttest_cmp expect actual &&\n+\n+\t\tgit -C \"$upstream\" show-ref >out &&\n+\t\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\t<COMMIT-B> refs/heads/bar\n+\t\t<COMMIT-B> refs/heads/master\n+\t\t<COMMIT-A> refs/heads/next\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\t# upstream  (before) : master(A)  bar(B)  baz(A)  next(A)\n+\t# upstream  (after)  : master(B)  bar(A)  baz(A)           foo(A)\n+\t# workbench (before) : master(B)  bar(A)  baz(A)  next(A)\n+\t# workbench (after)  : master(A)\n+\ttest_expect_success \"cleanup ($PROTOCOL)\" '\n+\t\t(\n+\t\t\tcd \"$upstream\" &&\n+\t\t\tgit update-ref refs/heads/master $B &&\n+\t\t\tgit update-ref refs/heads/foo    $A &&\n+\t\t\tgit update-ref refs/heads/bar    $A &&\n+\t\t\tgit update-ref refs/heads/baz    $A &&\n+\t\t\tgit update-ref -d refs/heads/next\n+\t\t) &&\n+\t\t(\n+\t\t\tcd \"workbench\" &&\n+\t\t\tgit update-ref refs/heads/master $A &&\n+\t\t\tgit update-ref -d refs/heads/bar &&\n+\t\t\tgit update-ref -d refs/heads/baz &&\n+\t\t\tgit update-ref -d refs/heads/next\n+\t\t)\n+\t'\n+}\n+\n+# Upstream  after setup : master(B)  foo(A)  bar(A)  baz(A)\n+# Workbench after setup : master(A)\n+test_expect_success \"setup\" '\n+\tgit init --bare upstream.git &&\n+\tgit init workbench &&\n+\tcreate_commits_in workbench A B &&\n+\t(\n+\t\tcd workbench &&\n+\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\tgit config core.abbrev 7 &&\n+\t\tgit remote add origin ../upstream.git &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit push origin \\\n+\t\t\t$B:refs/heads/master \\\n+\t\t\t$A:refs/heads/foo \\\n+\t\t\t$A:refs/heads/bar \\\n+\t\t\t$A:refs/heads/baz\n+\t) &&\n+\tgit -C \"workbench\" config advice.pushUpdateRejected false &&\n+\tupstream=upstream.git\n+'\n+\n+# Run git-push porcelain test on builtin protocol\n+run_git_push_porcelain_output_test file\n+\n+ROOT_PATH=\"$PWD\"\n+. \"$TEST_DIRECTORY\"/lib-gpg.sh\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+. \"$TEST_DIRECTORY\"/lib-terminal.sh\n+start_httpd\n+\n+test_expect_success \"setup for http\" '\n+\tgit -C upstream.git config http.receivepack true &&\n+\tupstream=\"$HTTPD_DOCUMENT_ROOT_PATH/upstream.git\" &&\n+\tmv upstream.git \"$upstream\" &&\n+\n+\tgit -C workbench remote set-url origin $HTTPD_URL/smart/upstream.git &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Run git-push porcelain test on HTTP protocol\n+run_git_push_porcelain_output_test http\n+\n+test_done\ndiff --git a/transport.c b/transport.c\nindex 1fdc7dac1a..13d638d5fe 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -715,7 +715,15 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re\n \n \tclose(data->fd[1]);\n \tclose(data->fd[0]);\n-\tret |= finish_connect(data->conn);\n+\t/*\n+\t * Atomic push may abort the connection early and close the pipe,\n+\t * which may cause an error for `finish_connect()`. Ignore this error\n+\t * for atomic git-push.\n+\t */\n+\tif (ret || args.atomic)\n+\t\tfinish_connect(data->conn);\n+\telse\n+\t\tret = finish_connect(data->conn);\n \tdata->conn = NULL;\n \tdata->got_remote_heads = 0;\n \n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395599","messageId":"20200416162415.5751-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200329143329.13846-1-worldhello.net@gmail.com","subject":"[PATCH v3 0/5] fix git-push porcelain output and atomic report issue","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-16T16:24:10Z","receivedAt":"2020-04-16T16:24:28Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nFind a new issue on porcelain output for git-push, add a new patch 1/5\nto fix this issue.  This fix overlaps with and the topic `jx/atomic-push`,\nso merge them together.\n\nI find this issue when adding test cases of porcelain output for topic\n`jx/proc-receive-hook`, which depends on this fix.\n\n## Details of this new issue\n\nThe porcelain output of a failed `git-push` command is inconsistent for\ndifferent protocols.  For example, the following `git-push` command\nfailed because of the failure of `pre-receive` hook.\n\n    git push --porcelain origin HEAD:refs/heads/master\n\nFor SSH protocol, the porcelain output does not end with a \"Done\"\nmessage:\n\n\tTo <URL/of/upstream.git>\n\t!  HEAD:refs/heads/master  [remote rejected] (pre-receive hook declined)\n\nWhile for HTTP protocol, the porcelain output does end with a \"Done\"\nmessage;\n\n\tTo <URL/of/upstream.git>\n\t!  HEAD:refs/heads/master  [remote rejected] (pre-receive hook declined)\n\tDone\n\nThe following code at the end of function `send_pack()` indicates that\n`send_pack()` should not return an error if some references are rejected\nin porcelain mode.\n\n    int send_pack(...)\n        ... ...\n\n        if (args->porcelain)\n            return 0;\n\n        for (ref = remote_refs; ref; ref = ref->next) {\n            switch (ref->status) {\n            case REF_STATUS_NONE:\n            case REF_STATUS_UPTODATE:\n            case REF_STATUS_OK:\n                break;\n            default:\n                return -1;\n            }\n        }\n        return 0;\n    }\n\nSo if atomic push failed, must check the porcelain mode before return\nan error.  And `receive_status()` should not return an error for a\nfailed updated reference, because `send_pack()` will check them instead.\n\n\n## Range-diff v2...v3\n\n\n-:  ---------- > 1:  d9ea3c35a3 send-pack: fix inconsistent porcelain output\n1:  7a0579ba13 = 2:  bb07d5c330 t5543: never report what we do not push\n2:  9b4bca8f4c ! 3:  1aa917b097 send-pack: mark failure of atomic push properly\n    @@ t/t5543-atomic-push.sh: test_expect_failure 'atomic push reports (mirror, but re\n      \t(\n      \t\tcd workbench &&\n     \n    + ## t/t5548-push-porcelain.sh ##\n    +@@ t/t5548-push-porcelain.sh: run_git_push_porcelain_output_test() {\n    + \t# Refs of upstream : master(A)  bar(B)  baz(A)  next(A)\n    + \t# Refs of workbench: master(B)  bar(A)  baz(A)  next(A)\n    + \t# git-push         : master(B)  bar(A)  NULL    next(A)\n    +-\ttest_expect_success \"atomic push failed ($PROTOCOL)\" '\n    ++\ttest_expect_failure \"atomic push failed ($PROTOCOL)\" '\n    + \t\t(\n    + \t\t\tcd workbench &&\n    + \t\t\tgit update-ref refs/heads/master $B &&\n    +\n      ## transport.c ##\n     @@ transport.c: int transport_push(struct repository *r,\n      \t\terr = push_had_errors(remote_refs);\n3:  a7e8d7c893 ! 4:  2848088852 transport-helper: mark failure for atomic push\n    @@ t/t5541-http-push-smart.sh: test_expect_failure 'push --atomic also prevents bra\n      \n      test_expect_success 'push --atomic fails on server-side errors' '\n     \n    + ## t/t5548-push-porcelain.sh ##\n    +@@ t/t5548-push-porcelain.sh: run_git_push_porcelain_output_test() {\n    + \t# Refs of upstream : master(A)  bar(B)  baz(A)  next(A)\n    + \t# Refs of workbench: master(B)  bar(A)  baz(A)  next(A)\n    + \t# git-push         : master(B)  bar(A)  NULL    next(A)\n    +-\ttest_expect_failure \"atomic push failed ($PROTOCOL)\" '\n    ++\ttest_expect_success \"atomic push failed ($PROTOCOL)\" '\n    + \t\t(\n    + \t\t\tcd workbench &&\n    + \t\t\tgit update-ref refs/heads/master $B &&\n    +@@ t/t5548-push-porcelain.sh: run_git_push_porcelain_output_test() {\n    + \t\tmake_user_friendly_and_stable_output <out >actual &&\n    + \t\tcat >expect <<-EOF &&\n    + \t\tTo <URL/of/upstream.git>\n    ++\t\t=    refs/heads/next:refs/heads/next    [up to date]\n    + \t\t!    refs/heads/bar:refs/heads/bar    [rejected] (non-fast-forward)\n    + \t\t!    (delete):refs/heads/baz    [rejected] (atomic push failed)\n    + \t\t!    refs/heads/master:refs/heads/master    [rejected] (atomic push failed)\n    +-\t\t!    refs/heads/next:refs/heads/next    [rejected] (atomic push failed)\n    + \t\tDone\n    + \t\tEOF\n    + \t\ttest_cmp expect actual &&\n    +@@ t/t5548-push-porcelain.sh: run_git_push_porcelain_output_test() {\n    + \t\tEOF\n    + \t\ttest_cmp expect actual\n    + \t'\n    +-\n    + \ttest_expect_success \"prepare pre-receive hook ($PROTOCOL)\" '\n    + \t\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n    + \t\texit 1\n    +\n      ## transport-helper.c ##\n     @@ transport-helper.c: static int push_refs_with_push(struct transport *transport,\n      \t\tcase REF_STATUS_REJECT_STALE:\n4:  94b13f5dcd ! 5:  d2f0b50395 transport-helper: new method reject_atomic_push()\n    @@ send-pack.c: int send_pack(struct send_pack_args *args,\n      \t\t\tif (use_atomic) {\n      \t\t\t\tstrbuf_release(&req_buf);\n      \t\t\t\tstrbuf_release(&cap_buf);\n    --\t\t\t\treturn atomic_push_failure(args, remote_refs, ref);\n    +-\t\t\t\tatomic_push_failure(args, remote_refs, ref);\n     +\t\t\t\treject_atomic_push(remote_refs, args->send_mirror);\n    -+\t\t\t\treturn error(\"atomic push failed for ref %s. status: %d\\n\",\n    -+\t\t\t\t\t     ref->name, ref->status);\n    ++\t\t\t\terror(\"atomic push failed for ref %s. status: %d\\n\",\n    ++\t\t\t\t      ref->name, ref->status);\n    + \t\t\t\treturn args->porcelain ? 0 : -1;\n      \t\t\t}\n      \t\t\t/* else fallthrough */\n    - \t\tdefault:\n     \n      ## transport-helper.c ##\n     @@ transport-helper.c: static int push_refs_with_push(struct transport *transport,\n\n---\n\nJiang Xin (5):\n  send-pack: fix inconsistent porcelain output\n  t5543: never report what we do not push\n  send-pack: mark failure of atomic push properly\n  transport-helper: mark failure for atomic push\n  transport-helper: new method reject_atomic_push()\n\n send-pack.c                |  32 +---\n t/t5541-http-push-smart.sh |  12 +-\n t/t5543-atomic-push.sh     |  89 +++++++++++\n t/t5548-push-porcelain.sh  | 300 +++++++++++++++++++++++++++++++++++++\n transport-helper.c         |  23 +++\n transport.c                |  24 ++-\n transport.h                |   3 +\n 7 files changed, 439 insertions(+), 44 deletions(-)\n create mode 100755 t/t5548-push-porcelain.sh\n\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395601","messageId":"20200416162415.5751-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200329143329.13846-1-worldhello.net@gmail.com","subject":"[PATCH v3 2/5] t5543: never report what we do not push","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-16T16:24:12Z","receivedAt":"2020-04-16T16:24:29Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen we push some references to the git server, we expect git to report\nthe status of the references we are pushing; no more, no less.  But when\npusing with atomic mode, if some references cannot be pushed, Git reports\nthe reject message on all references in the remote repository.\n\nAdd new test cases in t5543, and fix them in latter commit.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5543-atomic-push.sh | 89 ++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 89 insertions(+)\n\ndiff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh\nindex 7079bcf9a0..001240eec7 100755\n--- a/t/t5543-atomic-push.sh\n+++ b/t/t5543-atomic-push.sh\n@@ -27,6 +27,12 @@ test_refs () {\n \ttest_cmp expect actual\n }\n \n+fmt_status_report () {\n+\tsed -n \\\n+\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n+\t\t-e \"/^ ! / { s/   */ /g; p; }\"\n+}\n+\n test_expect_success 'atomic push works for a single branch' '\n \tmk_repo_pair &&\n \t(\n@@ -191,4 +197,87 @@ test_expect_success 'atomic push is not advertised if configured' '\n \ttest_refs master HEAD@{1}\n '\n \n+# References in upstream : master(1) one(1) foo(1)\n+# References in workbench: master(2)        foo(1) two(2) bar(2)\n+# Atomic push            : master(2)               two(2) bar(2)\n+test_expect_failure 'atomic push reports (reject by update hook)' '\n+\tmk_repo_pair &&\n+\t(\n+\t\tcd workbench &&\n+\t\ttest_commit one &&\n+\t\tgit branch foo &&\n+\t\tgit push up master one foo &&\n+\t\tgit tag -d one\n+\t) &&\n+\t(\n+\t\tmkdir -p upstream/.git/hooks &&\n+\t\tcat >upstream/.git/hooks/update <<-EOF &&\n+\t\t#!/bin/sh\n+\n+\t\tif test \"\\$1\" = \"refs/heads/bar\"\n+\t\tthen\n+\t\t\techo >&2 \"Pusing to branch bar is prohibited\"\n+\t\t\texit 1\n+\t\tfi\n+\t\tEOF\n+\t\tchmod a+x upstream/.git/hooks/update\n+\t) &&\n+\t(\n+\t\tcd workbench &&\n+\t\ttest_commit two &&\n+\t\tgit branch bar\n+\t) &&\n+\ttest_must_fail git -C workbench \\\n+\t\tpush --atomic up master two bar >out 2>&1 &&\n+\tfmt_status_report <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo ../upstream\n+\t ! [remote rejected] master -> master (atomic push failure)\n+\t ! [remote rejected] two -> two (atomic push failure)\n+\t ! [remote rejected] bar -> bar (hook declined)\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# References in upstream : master(1) one(1) foo(1)\n+# References in workbench: master(2)        foo(1) two(2) bar(2)\n+test_expect_failure 'atomic push reports (mirror, but reject by update hook)' '\n+\t(\n+\t\tcd workbench &&\n+\t\tgit remote remove up &&\n+\t\tgit remote add up ../upstream\n+\t) &&\n+\ttest_must_fail git -C workbench \\\n+\t\tpush --atomic --mirror up >out 2>&1 &&\n+\tfmt_status_report <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo ../upstream\n+\t ! [remote rejected] master -> master (atomic push failure)\n+\t ! [remote rejected] one (atomic push failure)\n+\t ! [remote rejected] bar -> bar (hook declined)\n+\t ! [remote rejected] two -> two (atomic push failure)\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# References in upstream : master(2) one(1) foo(1)\n+# References in workbench: master(1)        foo(1) two(2) bar(2)\n+test_expect_failure 'atomic push reports (reject by non-ff)' '\n+\trm upstream/.git/hooks/update &&\n+\t(\n+\t\tcd workbench &&\n+\t\tgit push up master &&\n+\t\tgit reset --hard HEAD^\n+\t) &&\n+\ttest_must_fail git -C workbench \\\n+\t\tpush --atomic up master foo bar >out 2>&1 &&\n+\tfmt_status_report <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo ../upstream\n+\t ! [rejected] master -> master (non-fast-forward)\n+\t ! [rejected] bar -> bar (atomic push failed)\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395602","messageId":"20200416162415.5751-4-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200329143329.13846-1-worldhello.net@gmail.com","subject":"[PATCH v3 3/5] send-pack: mark failure of atomic push properly","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-16T16:24:13Z","receivedAt":"2020-04-16T16:24:31Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing with SSH or other smart protocol, references are validated\nby function `check_to_send_update()` before they are sent in commands\nto `send_pack()` of \"receve-pack\".  For atomic push, if a reference is\nrejected after the validation, only references pushed by user should be\nmarked as failure, instead of report failure on all remote references.\n\nCommit v2.22.0-1-g3bca1e7f9f (transport-helper: enforce atomic in\npush_refs_with_push, 2019-07-11) wanted to fix report issue of HTTP\nprotocol, but marked all remote references failure for atomic push.\n\nIn order to fix the issue of status report for SSH or other built-in\nsmart protocol, revert part of that commit and add additional status\nfor function `atomic_push_failure()`.  The additional status for it\nexcept the \"REF_STATUS_EXPECTING_REPORT\" status are:\n\n- REF_STATUS_NONE : Not marked as \"REF_STATUS_EXPECTING_REPORT\" yet.\n- REF_STATUS_OK   : Assume OK for dryrun or status_report is disabled.\n\nThis fix won't resolve the issue of status report in transport-helper\nfor HTTP or other protocols, and breaks test case in t5541.  Will fix\nit in additional commit.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n send-pack.c                |  2 ++\n t/t5541-http-push-smart.sh |  2 +-\n t/t5543-atomic-push.sh     |  6 +++---\n t/t5548-push-porcelain.sh  |  2 +-\n transport.c                | 14 --------------\n 5 files changed, 7 insertions(+), 19 deletions(-)\n\ndiff --git a/send-pack.c b/send-pack.c\nindex 1835cd5582..efefb687b2 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -332,6 +332,8 @@ static int atomic_push_failure(struct send_pack_args *args,\n \t\t\tcontinue;\n \n \t\tswitch (ref->status) {\n+\t\tcase REF_STATUS_NONE:\n+\t\tcase REF_STATUS_OK:\n \t\tcase REF_STATUS_EXPECTING_REPORT:\n \t\t\tref->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n \t\t\tcontinue;\ndiff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh\nindex 23be8ce92d..2c2c3fb0f5 100755\n--- a/t/t5541-http-push-smart.sh\n+++ b/t/t5541-http-push-smart.sh\n@@ -177,7 +177,7 @@ test_expect_success 'push (chunked)' '\n \t test $HEAD = $(git rev-parse --verify HEAD))\n '\n \n-test_expect_success 'push --atomic also prevents branch creation, reports collateral' '\n+test_expect_failure 'push --atomic also prevents branch creation, reports collateral' '\n \t# Setup upstream repo - empty for now\n \td=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&\n \tgit init --bare \"$d\" &&\ndiff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh\nindex 001240eec7..620c30d58f 100755\n--- a/t/t5543-atomic-push.sh\n+++ b/t/t5543-atomic-push.sh\n@@ -200,7 +200,7 @@ test_expect_success 'atomic push is not advertised if configured' '\n # References in upstream : master(1) one(1) foo(1)\n # References in workbench: master(2)        foo(1) two(2) bar(2)\n # Atomic push            : master(2)               two(2) bar(2)\n-test_expect_failure 'atomic push reports (reject by update hook)' '\n+test_expect_success 'atomic push reports (reject by update hook)' '\n \tmk_repo_pair &&\n \t(\n \t\tcd workbench &&\n@@ -241,7 +241,7 @@ test_expect_failure 'atomic push reports (reject by update hook)' '\n \n # References in upstream : master(1) one(1) foo(1)\n # References in workbench: master(2)        foo(1) two(2) bar(2)\n-test_expect_failure 'atomic push reports (mirror, but reject by update hook)' '\n+test_expect_success 'atomic push reports (mirror, but reject by update hook)' '\n \t(\n \t\tcd workbench &&\n \t\tgit remote remove up &&\n@@ -262,7 +262,7 @@ test_expect_failure 'atomic push reports (mirror, but reject by update hook)' '\n \n # References in upstream : master(2) one(1) foo(1)\n # References in workbench: master(1)        foo(1) two(2) bar(2)\n-test_expect_failure 'atomic push reports (reject by non-ff)' '\n+test_expect_success 'atomic push reports (reject by non-ff)' '\n \trm upstream/.git/hooks/update &&\n \t(\n \t\tcd workbench &&\ndiff --git a/t/t5548-push-porcelain.sh b/t/t5548-push-porcelain.sh\nindex 8d487b2a7a..e7d9e06dac 100755\n--- a/t/t5548-push-porcelain.sh\n+++ b/t/t5548-push-porcelain.sh\n@@ -110,7 +110,7 @@ run_git_push_porcelain_output_test() {\n \t# Refs of upstream : master(A)  bar(B)  baz(A)  next(A)\n \t# Refs of workbench: master(B)  bar(A)  baz(A)  next(A)\n \t# git-push         : master(B)  bar(A)  NULL    next(A)\n-\ttest_expect_success \"atomic push failed ($PROTOCOL)\" '\n+\ttest_expect_failure \"atomic push failed ($PROTOCOL)\" '\n \t\t(\n \t\t\tcd workbench &&\n \t\t\tgit update-ref refs/heads/master $B &&\ndiff --git a/transport.c b/transport.c\nindex 13d638d5fe..a07e39564c 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1248,20 +1248,6 @@ int transport_push(struct repository *r,\n \t\terr = push_had_errors(remote_refs);\n \t\tret = push_ret | err;\n \n-\t\tif ((flags & TRANSPORT_PUSH_ATOMIC) && err) {\n-\t\t\tstruct ref *it;\n-\t\t\tfor (it = remote_refs; it; it = it->next)\n-\t\t\t\tswitch (it->status) {\n-\t\t\t\tcase REF_STATUS_NONE:\n-\t\t\t\tcase REF_STATUS_UPTODATE:\n-\t\t\t\tcase REF_STATUS_OK:\n-\t\t\t\t\tit->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n-\t\t\t\t\tbreak;\n-\t\t\t\tdefault:\n-\t\t\t\t\tbreak;\n-\t\t\t\t}\n-\t\t}\n-\n \t\tif (!quiet || err)\n \t\t\ttransport_print_push_status(transport->url, remote_refs,\n \t\t\t\t\tverbose | porcelain, porcelain,\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395603","messageId":"20200416162415.5751-5-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200329143329.13846-1-worldhello.net@gmail.com","subject":"[PATCH v3 4/5] transport-helper: mark failure for atomic push","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-16T16:24:14Z","receivedAt":"2020-04-16T16:24:33Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nCommit v2.22.0-1-g3bca1e7f9f (transport-helper: enforce atomic in\npush_refs_with_push, 2019-07-11) noticed the incomplete report of\nfailure of an atomic push for HTTP protocol.  But the implementation\nhas a flaw that mark all remote references as failure.\n\nOnly mark necessary references as failure in `push_refs_with_push()` of\ntransport-helper.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5541-http-push-smart.sh | 14 +++++++++++---\n t/t5548-push-porcelain.sh  |  5 ++---\n transport-helper.c         | 15 +++++++++++++++\n 3 files changed, 28 insertions(+), 6 deletions(-)\n\ndiff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh\nindex 2c2c3fb0f5..afc680d5e3 100755\n--- a/t/t5541-http-push-smart.sh\n+++ b/t/t5541-http-push-smart.sh\n@@ -177,7 +177,10 @@ test_expect_success 'push (chunked)' '\n \t test $HEAD = $(git rev-parse --verify HEAD))\n '\n \n-test_expect_failure 'push --atomic also prevents branch creation, reports collateral' '\n+## References of remote: atomic1(1)            master(2) collateral(2) other(2)\n+## References of local :            atomic2(2) master(1) collateral(3) other(2) collateral1(3) atomic(1)\n+## Atomic push         :                       master(1) collateral(3)                         atomic(1)\n+test_expect_success 'push --atomic also prevents branch creation, reports collateral' '\n \t# Setup upstream repo - empty for now\n \td=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&\n \tgit init --bare \"$d\" &&\n@@ -189,7 +192,8 @@ test_expect_failure 'push --atomic also prevents branch creation, reports collat\n \ttest_commit atomic2 &&\n \tgit branch collateral &&\n \tgit branch other &&\n-\tgit push \"$up\" master collateral other &&\n+\tgit push \"$up\" atomic1 master collateral other &&\n+\tgit tag -d atomic1 &&\n \n \t# collateral is a valid push, but should be failed by atomic push\n \tgit checkout collateral &&\n@@ -224,7 +228,11 @@ test_expect_failure 'push --atomic also prevents branch creation, reports collat\n \n \t# the collateral failure refs should be indicated to the user\n \tgrep \"^ ! .*rejected.* atomic -> atomic .*atomic push failed\" output &&\n-\tgrep \"^ ! .*rejected.* collateral -> collateral .*atomic push failed\" output\n+\tgrep \"^ ! .*rejected.* collateral -> collateral .*atomic push failed\" output &&\n+\n+\t# never report what we do not push\n+\t! grep \"^ ! .*rejected.* atomic1 \" output &&\n+\t! grep \"^ ! .*rejected.* other \" output\n '\n \n test_expect_success 'push --atomic fails on server-side errors' '\ndiff --git a/t/t5548-push-porcelain.sh b/t/t5548-push-porcelain.sh\nindex e7d9e06dac..898099aede 100755\n--- a/t/t5548-push-porcelain.sh\n+++ b/t/t5548-push-porcelain.sh\n@@ -110,7 +110,7 @@ run_git_push_porcelain_output_test() {\n \t# Refs of upstream : master(A)  bar(B)  baz(A)  next(A)\n \t# Refs of workbench: master(B)  bar(A)  baz(A)  next(A)\n \t# git-push         : master(B)  bar(A)  NULL    next(A)\n-\ttest_expect_failure \"atomic push failed ($PROTOCOL)\" '\n+\ttest_expect_success \"atomic push failed ($PROTOCOL)\" '\n \t\t(\n \t\t\tcd workbench &&\n \t\t\tgit update-ref refs/heads/master $B &&\n@@ -124,10 +124,10 @@ run_git_push_porcelain_output_test() {\n \t\tmake_user_friendly_and_stable_output <out >actual &&\n \t\tcat >expect <<-EOF &&\n \t\tTo <URL/of/upstream.git>\n+\t\t=    refs/heads/next:refs/heads/next    [up to date]\n \t\t!    refs/heads/bar:refs/heads/bar    [rejected] (non-fast-forward)\n \t\t!    (delete):refs/heads/baz    [rejected] (atomic push failed)\n \t\t!    refs/heads/master:refs/heads/master    [rejected] (atomic push failed)\n-\t\t!    refs/heads/next:refs/heads/next    [rejected] (atomic push failed)\n \t\tDone\n \t\tEOF\n \t\ttest_cmp expect actual &&\n@@ -142,7 +142,6 @@ run_git_push_porcelain_output_test() {\n \t\tEOF\n \t\ttest_cmp expect actual\n \t'\n-\n \ttest_expect_success \"prepare pre-receive hook ($PROTOCOL)\" '\n \t\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n \t\texit 1\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 20a7185ec4..ab3b52eb14 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -894,6 +894,21 @@ static int push_refs_with_push(struct transport *transport,\n \t\tcase REF_STATUS_REJECT_STALE:\n \t\tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n \t\t\tif (atomic) {\n+\t\t\t\t/* Mark other refs as failed */\n+\t\t\t\tfor (ref = remote_refs; ref; ref = ref->next) {\n+\t\t\t\t\tif (!ref->peer_ref && !mirror)\n+\t\t\t\t\t\tcontinue;\n+\n+\t\t\t\t\tswitch (ref->status) {\n+\t\t\t\t\tcase REF_STATUS_NONE:\n+\t\t\t\t\tcase REF_STATUS_OK:\n+\t\t\t\t\tcase REF_STATUS_EXPECTING_REPORT:\n+\t\t\t\t\t\tref->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\tdefault:\n+\t\t\t\t\t\tbreak; /* do nothing */\n+\t\t\t\t\t}\n+\t\t\t\t}\n \t\t\t\tstring_list_clear(&cas_options, 0);\n \t\t\t\treturn 0;\n \t\t\t} else\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395604","messageId":"20200416162415.5751-6-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200329143329.13846-1-worldhello.net@gmail.com","subject":"[PATCH v3 5/5] transport-helper: new method reject_atomic_push()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-16T16:24:15Z","receivedAt":"2020-04-16T16:24:34Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd new method in transport-helper to reject all references if any\nreference is failed for atomic push.\n\nThis method is reused in \"send-pack.c\" and \"transport-helper.c\", one for\nSSH, git and file protocols, and the other for HTTP protocol.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n send-pack.c        | 29 +++--------------------------\n transport-helper.c | 38 +++++++++++++++++++++++---------------\n transport.h        |  3 +++\n 3 files changed, 29 insertions(+), 41 deletions(-)\n\ndiff --git a/send-pack.c b/send-pack.c\nindex efefb687b2..a7c53193c9 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -320,31 +320,6 @@ static int generate_push_cert(struct strbuf *req_buf,\n \treturn update_seen;\n }\n \n-\n-static int atomic_push_failure(struct send_pack_args *args,\n-\t\t\t       struct ref *remote_refs,\n-\t\t\t       struct ref *failing_ref)\n-{\n-\tstruct ref *ref;\n-\t/* Mark other refs as failed */\n-\tfor (ref = remote_refs; ref; ref = ref->next) {\n-\t\tif (!ref->peer_ref && !args->send_mirror)\n-\t\t\tcontinue;\n-\n-\t\tswitch (ref->status) {\n-\t\tcase REF_STATUS_NONE:\n-\t\tcase REF_STATUS_OK:\n-\t\tcase REF_STATUS_EXPECTING_REPORT:\n-\t\t\tref->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n-\t\t\tcontinue;\n-\t\tdefault:\n-\t\t\tbreak; /* do nothing */\n-\t\t}\n-\t}\n-\treturn error(\"atomic push failed for ref %s. status: %d\\n\",\n-\t\t     failing_ref->name, failing_ref->status);\n-}\n-\n #define NONCE_LEN_LIMIT 256\n \n static void reject_invalid_nonce(const char *nonce, int len)\n@@ -489,7 +464,9 @@ int send_pack(struct send_pack_args *args,\n \t\t\tif (use_atomic) {\n \t\t\t\tstrbuf_release(&req_buf);\n \t\t\t\tstrbuf_release(&cap_buf);\n-\t\t\t\tatomic_push_failure(args, remote_refs, ref);\n+\t\t\t\treject_atomic_push(remote_refs, args->send_mirror);\n+\t\t\t\terror(\"atomic push failed for ref %s. status: %d\\n\",\n+\t\t\t\t      ref->name, ref->status);\n \t\t\t\treturn args->porcelain ? 0 : -1;\n \t\t\t}\n \t\t\t/* else fallthrough */\ndiff --git a/transport-helper.c b/transport-helper.c\nindex ab3b52eb14..a46afcb69d 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -894,21 +894,7 @@ static int push_refs_with_push(struct transport *transport,\n \t\tcase REF_STATUS_REJECT_STALE:\n \t\tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n \t\t\tif (atomic) {\n-\t\t\t\t/* Mark other refs as failed */\n-\t\t\t\tfor (ref = remote_refs; ref; ref = ref->next) {\n-\t\t\t\t\tif (!ref->peer_ref && !mirror)\n-\t\t\t\t\t\tcontinue;\n-\n-\t\t\t\t\tswitch (ref->status) {\n-\t\t\t\t\tcase REF_STATUS_NONE:\n-\t\t\t\t\tcase REF_STATUS_OK:\n-\t\t\t\t\tcase REF_STATUS_EXPECTING_REPORT:\n-\t\t\t\t\t\tref->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n-\t\t\t\t\t\tcontinue;\n-\t\t\t\t\tdefault:\n-\t\t\t\t\t\tbreak; /* do nothing */\n-\t\t\t\t\t}\n-\t\t\t\t}\n+\t\t\t\treject_atomic_push(remote_refs, mirror);\n \t\t\t\tstring_list_clear(&cas_options, 0);\n \t\t\t\treturn 0;\n \t\t\t} else\n@@ -1503,3 +1489,25 @@ int bidirectional_transfer_loop(int input, int output)\n \n \treturn tloop_spawnwait_tasks(&state);\n }\n+\n+void reject_atomic_push(struct ref *remote_refs, int mirror_mode)\n+{\n+\tstruct ref *ref;\n+\n+\t/* Mark other refs as failed */\n+\tfor (ref = remote_refs; ref; ref = ref->next) {\n+\t\tif (!ref->peer_ref && !mirror_mode)\n+\t\t\tcontinue;\n+\n+\t\tswitch (ref->status) {\n+\t\tcase REF_STATUS_NONE:\n+\t\tcase REF_STATUS_OK:\n+\t\tcase REF_STATUS_EXPECTING_REPORT:\n+\t\t\tref->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n+\t\t\tcontinue;\n+\t\tdefault:\n+\t\t\tbreak; /* do nothing */\n+\t\t}\n+\t}\n+\treturn;\n+}\ndiff --git a/transport.h b/transport.h\nindex e0131daab9..4298c855be 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -265,4 +265,7 @@ int transport_refs_pushed(struct ref *ref);\n void transport_print_push_status(const char *dest, struct ref *refs,\n \t\t  int verbose, int porcelain, unsigned int *reject_reasons);\n \n+/* common method used by transport-helper.c and send-pack.c */\n+void reject_atomic_push(struct ref *refs, int mirror_mode);\n+\n #endif\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395651","messageId":"20200417094536.9463-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200416162415.5751-1-worldhello.net@gmail.com","subject":"[PATCH v4 0/5] fix git-push porcelain output and atomic report issue","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-17T09:45:31Z","receivedAt":"2020-04-17T09:45:43Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nPatch 1/5 fixes format of porcelain output for `git-push`, and it should\nbelong to a new topic, but it has a overlap with topic `jx/atomic-push`.\n\nSo I rebased `jx/atomic-push` on top of it, and resolved conflicts\nbetween these two features.  Patch 2/5 ~ 5/5 are belong to the original\ntopic `jx/atomic-push`.\n\nNote: Some patches of `jx/proc-receive-hook` also have overlap with this\n      patch series, so v13 of `jx/proc-receive-hook` will base on this\n      topic, instead of master branch of Git.\n\n## Changes since `jx/atomic-push` v3:\n\n* Fix broken test cases in t5504, and t5516, which is instroduced by\n  patch 1/5..\n\n## Changes since `jx/atomic-push` v2:\n\nAdd new patch 1/5 which fixes new issue found in git-push porcelain output.\nThis fix overlaps with and the topic `jx/atomic-push`, so merge them together.\n\nI find this issue when adding test cases of porcelain output for topic\n`jx/proc-receive-hook`, which depends on this fix.\n\nDetails of this new issue:\n\nThe porcelain output of a failed `git-push` command is inconsistent for\ndifferent protocols.  For example, the following `git-push` command\nmay fail due to the failure of the `pre-receive` hook.\n\n    git push --porcelain origin HEAD:refs/heads/master\n\nFor SSH protocol, the porcelain output does not end with a \"Done\"\nmessage:\n\n\tTo <URL/of/upstream.git>\n\t!  HEAD:refs/heads/master  [remote rejected] (pre-receive hook declined)\n\nWhile for HTTP protocol, the porcelain output does end with a \"Done\"\nmessage:\n\n\tTo <URL/of/upstream.git>\n\t!  HEAD:refs/heads/master  [remote rejected] (pre-receive hook declined)\n\tDone\n\nThe following code at the end of function `send_pack()` indicates that\n`send_pack()` should not return an error if some references are rejected\nin porcelain mode.\n\n    int send_pack(...)\n        ... ...\n\n        if (args->porcelain)\n            return 0;\n\n        for (ref = remote_refs; ref; ref = ref->next) {\n            switch (ref->status) {\n            case REF_STATUS_NONE:\n            case REF_STATUS_UPTODATE:\n            case REF_STATUS_OK:\n                break;\n            default:\n                return -1;\n            }\n        }\n        return 0;\n    }\n\nSo if atomic push failed, must check the porcelain mode before return\nan error.  And `receive_status()` should not return an error for a\nfailed updated reference, because `send_pack()` will check them instead.\n\n\n## Range-diff v2...v4\n\n-:  ---------- > 1:  96028ebf7d send-pack: fix inconsistent porcelain output\n1:  7a0579ba13 = 2:  77f66ebfd2 t5543: never report what we do not push\n2:  9b4bca8f4c ! 3:  cb63c4a9f4 send-pack: mark failure of atomic push properly\n    @@ t/t5543-atomic-push.sh: test_expect_failure 'atomic push reports (mirror, but re\n      \t(\n      \t\tcd workbench &&\n     \n    + ## t/t5548-push-porcelain.sh ##\n    +@@ t/t5548-push-porcelain.sh: run_git_push_porcelain_output_test() {\n    + \t# Refs of upstream : master(A)  bar(B)  baz(A)  next(A)\n    + \t# Refs of workbench: master(B)  bar(A)  baz(A)  next(A)\n    + \t# git-push         : master(B)  bar(A)  NULL    next(A)\n    +-\ttest_expect_success \"atomic push failed ($PROTOCOL)\" '\n    ++\ttest_expect_failure \"atomic push failed ($PROTOCOL)\" '\n    + \t\t(\n    + \t\t\tcd workbench &&\n    + \t\t\tgit update-ref refs/heads/master $B &&\n    +\n      ## transport.c ##\n     @@ transport.c: int transport_push(struct repository *r,\n      \t\terr = push_had_errors(remote_refs);\n3:  a7e8d7c893 ! 4:  3fb57569da transport-helper: mark failure for atomic push\n    @@ t/t5541-http-push-smart.sh: test_expect_failure 'push --atomic also prevents bra\n      \n      test_expect_success 'push --atomic fails on server-side errors' '\n     \n    + ## t/t5548-push-porcelain.sh ##\n    +@@ t/t5548-push-porcelain.sh: run_git_push_porcelain_output_test() {\n    + \t# Refs of upstream : master(A)  bar(B)  baz(A)  next(A)\n    + \t# Refs of workbench: master(B)  bar(A)  baz(A)  next(A)\n    + \t# git-push         : master(B)  bar(A)  NULL    next(A)\n    +-\ttest_expect_failure \"atomic push failed ($PROTOCOL)\" '\n    ++\ttest_expect_success \"atomic push failed ($PROTOCOL)\" '\n    + \t\t(\n    + \t\t\tcd workbench &&\n    + \t\t\tgit update-ref refs/heads/master $B &&\n    +@@ t/t5548-push-porcelain.sh: run_git_push_porcelain_output_test() {\n    + \t\tmake_user_friendly_and_stable_output <out >actual &&\n    + \t\tcat >expect <<-EOF &&\n    + \t\tTo <URL/of/upstream.git>\n    ++\t\t=    refs/heads/next:refs/heads/next    [up to date]\n    + \t\t!    refs/heads/bar:refs/heads/bar    [rejected] (non-fast-forward)\n    + \t\t!    (delete):refs/heads/baz    [rejected] (atomic push failed)\n    + \t\t!    refs/heads/master:refs/heads/master    [rejected] (atomic push failed)\n    +-\t\t!    refs/heads/next:refs/heads/next    [rejected] (atomic push failed)\n    + \t\tDone\n    + \t\tEOF\n    + \t\ttest_cmp expect actual &&\n    +@@ t/t5548-push-porcelain.sh: run_git_push_porcelain_output_test() {\n    + \t\tEOF\n    + \t\ttest_cmp expect actual\n    + \t'\n    +-\n    + \ttest_expect_success \"prepare pre-receive hook ($PROTOCOL)\" '\n    + \t\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n    + \t\texit 1\n    +\n      ## transport-helper.c ##\n     @@ transport-helper.c: static int push_refs_with_push(struct transport *transport,\n      \t\tcase REF_STATUS_REJECT_STALE:\n4:  94b13f5dcd ! 5:  1c73ddff0d transport-helper: new method reject_atomic_push()\n    @@ send-pack.c: int send_pack(struct send_pack_args *args,\n      \t\t\tif (use_atomic) {\n      \t\t\t\tstrbuf_release(&req_buf);\n      \t\t\t\tstrbuf_release(&cap_buf);\n    --\t\t\t\treturn atomic_push_failure(args, remote_refs, ref);\n    +-\t\t\t\tatomic_push_failure(args, remote_refs, ref);\n     +\t\t\t\treject_atomic_push(remote_refs, args->send_mirror);\n    -+\t\t\t\treturn error(\"atomic push failed for ref %s. status: %d\\n\",\n    -+\t\t\t\t\t     ref->name, ref->status);\n    ++\t\t\t\terror(\"atomic push failed for ref %s. status: %d\\n\",\n    ++\t\t\t\t      ref->name, ref->status);\n    + \t\t\t\treturn args->porcelain ? 0 : -1;\n      \t\t\t}\n      \t\t\t/* else fallthrough */\n    - \t\tdefault:\n     \n      ## transport-helper.c ##\n     @@ transport-helper.c: static int push_refs_with_push(struct transport *transport,\n\n---\n\nJiang Xin (5):\n  send-pack: fix inconsistent porcelain output\n  t5543: never report what we do not push\n  send-pack: mark failure of atomic push properly\n  transport-helper: mark failure for atomic push\n  transport-helper: new method reject_atomic_push()\n\n send-pack.c                     |  32 +---\n t/t5504-fetch-receive-strict.sh |   1 +\n t/t5516-fetch-push.sh           |   1 +\n t/t5541-http-push-smart.sh      |  12 +-\n t/t5543-atomic-push.sh          |  89 ++++++++++\n t/t5548-push-porcelain.sh       | 300 ++++++++++++++++++++++++++++++++\n transport-helper.c              |  23 +++\n transport.c                     |  24 +--\n transport.h                     |   3 +\n 9 files changed, 441 insertions(+), 44 deletions(-)\n create mode 100755 t/t5548-push-porcelain.sh\n\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395652","messageId":"20200417094536.9463-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200416162415.5751-1-worldhello.net@gmail.com","subject":"[PATCH v4 2/5] t5543: never report what we do not push","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-17T09:45:33Z","receivedAt":"2020-04-17T09:45:45Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen we push some references to the git server, we expect git to report\nthe status of the references we are pushing; no more, no less.  But when\npusing with atomic mode, if some references cannot be pushed, Git reports\nthe reject message on all references in the remote repository.\n\nAdd new test cases in t5543, and fix them in latter commit.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5543-atomic-push.sh | 89 ++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 89 insertions(+)\n\ndiff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh\nindex 7079bcf9a0..001240eec7 100755\n--- a/t/t5543-atomic-push.sh\n+++ b/t/t5543-atomic-push.sh\n@@ -27,6 +27,12 @@ test_refs () {\n \ttest_cmp expect actual\n }\n \n+fmt_status_report () {\n+\tsed -n \\\n+\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n+\t\t-e \"/^ ! / { s/   */ /g; p; }\"\n+}\n+\n test_expect_success 'atomic push works for a single branch' '\n \tmk_repo_pair &&\n \t(\n@@ -191,4 +197,87 @@ test_expect_success 'atomic push is not advertised if configured' '\n \ttest_refs master HEAD@{1}\n '\n \n+# References in upstream : master(1) one(1) foo(1)\n+# References in workbench: master(2)        foo(1) two(2) bar(2)\n+# Atomic push            : master(2)               two(2) bar(2)\n+test_expect_failure 'atomic push reports (reject by update hook)' '\n+\tmk_repo_pair &&\n+\t(\n+\t\tcd workbench &&\n+\t\ttest_commit one &&\n+\t\tgit branch foo &&\n+\t\tgit push up master one foo &&\n+\t\tgit tag -d one\n+\t) &&\n+\t(\n+\t\tmkdir -p upstream/.git/hooks &&\n+\t\tcat >upstream/.git/hooks/update <<-EOF &&\n+\t\t#!/bin/sh\n+\n+\t\tif test \"\\$1\" = \"refs/heads/bar\"\n+\t\tthen\n+\t\t\techo >&2 \"Pusing to branch bar is prohibited\"\n+\t\t\texit 1\n+\t\tfi\n+\t\tEOF\n+\t\tchmod a+x upstream/.git/hooks/update\n+\t) &&\n+\t(\n+\t\tcd workbench &&\n+\t\ttest_commit two &&\n+\t\tgit branch bar\n+\t) &&\n+\ttest_must_fail git -C workbench \\\n+\t\tpush --atomic up master two bar >out 2>&1 &&\n+\tfmt_status_report <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo ../upstream\n+\t ! [remote rejected] master -> master (atomic push failure)\n+\t ! [remote rejected] two -> two (atomic push failure)\n+\t ! [remote rejected] bar -> bar (hook declined)\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# References in upstream : master(1) one(1) foo(1)\n+# References in workbench: master(2)        foo(1) two(2) bar(2)\n+test_expect_failure 'atomic push reports (mirror, but reject by update hook)' '\n+\t(\n+\t\tcd workbench &&\n+\t\tgit remote remove up &&\n+\t\tgit remote add up ../upstream\n+\t) &&\n+\ttest_must_fail git -C workbench \\\n+\t\tpush --atomic --mirror up >out 2>&1 &&\n+\tfmt_status_report <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo ../upstream\n+\t ! [remote rejected] master -> master (atomic push failure)\n+\t ! [remote rejected] one (atomic push failure)\n+\t ! [remote rejected] bar -> bar (hook declined)\n+\t ! [remote rejected] two -> two (atomic push failure)\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# References in upstream : master(2) one(1) foo(1)\n+# References in workbench: master(1)        foo(1) two(2) bar(2)\n+test_expect_failure 'atomic push reports (reject by non-ff)' '\n+\trm upstream/.git/hooks/update &&\n+\t(\n+\t\tcd workbench &&\n+\t\tgit push up master &&\n+\t\tgit reset --hard HEAD^\n+\t) &&\n+\ttest_must_fail git -C workbench \\\n+\t\tpush --atomic up master foo bar >out 2>&1 &&\n+\tfmt_status_report <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo ../upstream\n+\t ! [rejected] master -> master (non-fast-forward)\n+\t ! [rejected] bar -> bar (atomic push failed)\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395653","messageId":"20200417094536.9463-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200416162415.5751-1-worldhello.net@gmail.com","subject":"[PATCH v4 1/5] send-pack: fix inconsistent porcelain output","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-17T09:45:32Z","receivedAt":"2020-04-17T09:45:45Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThe porcelain output of a failed `git-push` command is inconsistent for\ndifferent protocols.  For example, the following `git-push` command\nmay fail due to the failure of the `pre-receive` hook.\n\n    git push --porcelain origin HEAD:refs/heads/master\n\nFor SSH protocol, the porcelain output does not end with a \"Done\"\nmessage:\n\n\tTo <URL/of/upstream.git>\n\t!  HEAD:refs/heads/master  [remote rejected] (pre-receive hook declined)\n\nWhile for HTTP protocol, the porcelain output does end with a \"Done\"\nmessage:\n\n\tTo <URL/of/upstream.git>\n\t!  HEAD:refs/heads/master  [remote rejected] (pre-receive hook declined)\n\tDone\n\nThe following code at the end of function `send_pack()` indicates that\n`send_pack()` should not return an error if some references are rejected\nin porcelain mode.\n\n    int send_pack(...)\n        ... ...\n\n        if (args->porcelain)\n            return 0;\n\n        for (ref = remote_refs; ref; ref = ref->next) {\n            switch (ref->status) {\n            case REF_STATUS_NONE:\n            case REF_STATUS_UPTODATE:\n            case REF_STATUS_OK:\n                break;\n            default:\n                return -1;\n            }\n        }\n        return 0;\n    }\n\nSo if atomic push failed, must check the porcelain mode before return\nan error.  And `receive_status()` should not return an error for a\nfailed updated reference, because `send_pack()` will check them instead.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n send-pack.c                     |   7 +-\n t/t5504-fetch-receive-strict.sh |   1 +\n t/t5516-fetch-push.sh           |   1 +\n t/t5548-push-porcelain.sh       | 280 ++++++++++++++++++++++++++++++++\n transport.c                     |  10 +-\n 5 files changed, 294 insertions(+), 5 deletions(-)\n create mode 100755 t/t5548-push-porcelain.sh\n\ndiff --git a/send-pack.c b/send-pack.c\nindex 0407841ae8..1835cd5582 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -190,10 +190,8 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \n \t\tif (reader->line[0] == 'o' && reader->line[1] == 'k')\n \t\t\thint->status = REF_STATUS_OK;\n-\t\telse {\n+\t\telse\n \t\t\thint->status = REF_STATUS_REMOTE_REJECT;\n-\t\t\tret = -1;\n-\t\t}\n \t\thint->remote_status = xstrdup_or_null(msg);\n \t\t/* start our next search from the next ref */\n \t\thint = hint->next;\n@@ -489,7 +487,8 @@ int send_pack(struct send_pack_args *args,\n \t\t\tif (use_atomic) {\n \t\t\t\tstrbuf_release(&req_buf);\n \t\t\t\tstrbuf_release(&cap_buf);\n-\t\t\t\treturn atomic_push_failure(args, remote_refs, ref);\n+\t\t\t\tatomic_push_failure(args, remote_refs, ref);\n+\t\t\t\treturn args->porcelain ? 0 : -1;\n \t\t\t}\n \t\t\t/* else fallthrough */\n \t\tdefault:\ndiff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh\nindex 645b4c78d3..a32efe2b6c 100755\n--- a/t/t5504-fetch-receive-strict.sh\n+++ b/t/t5504-fetch-receive-strict.sh\n@@ -65,6 +65,7 @@ test_expect_success 'fetch with transfer.fsckobjects' '\n cat >exp <<EOF\n To dst\n !\trefs/heads/master:refs/heads/test\t[remote rejected] (missing necessary objects)\n+Done\n EOF\n \n test_expect_success 'push without strict' '\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 9ff041a093..9c6218f568 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1066,6 +1066,7 @@ test_expect_success 'push --porcelain rejected' '\n \n \techo >.git/foo  \"To testrepo\"  &&\n \techo >>.git/foo \"!\trefs/heads/master:refs/heads/master\t[remote rejected] (branch is currently checked out)\" &&\n+\techo >>.git/foo \"Done\" &&\n \n \ttest_must_fail git push >.git/bar --porcelain  testrepo refs/heads/master:refs/heads/master &&\n \ttest_cmp .git/foo .git/bar\ndiff --git a/t/t5548-push-porcelain.sh b/t/t5548-push-porcelain.sh\nnew file mode 100755\nindex 0000000000..96ba449a38\n--- /dev/null\n+++ b/t/t5548-push-porcelain.sh\n@@ -0,0 +1,280 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+test_description='Test git push porcelain output'\n+\n+. ./test-lib.sh\n+\n+# Create commits in <repo> and assign each commit's oid to shell variables\n+# given in the arguments (A, B, and C). E.g.:\n+#\n+#     create_commits_in <repo> A B C\n+#\n+# NOTE: Never calling this function from a subshell since variable\n+# assignments will disappear when subshell exits.\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{} --)\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  We can easily prepare the expect text\n+# without having to worry about future changes of the commit ID and spaces\n+# of the output.\n+make_user_friendly_and_stable_output () {\n+\tsed \\\n+\t\t-e \"s/  *\\$//\" \\\n+\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/\t/    /g\" \\\n+\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n+\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n+\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n+\t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n+\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\" \\\n+\t\t-e \"s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#\"\n+}\n+\n+setup_upstream_and_workbench () {\n+\t# Upstream  after setup : master(B)  foo(A)  bar(A)  baz(A)\n+\t# Workbench after setup : master(A)\n+\ttest_expect_success \"setup upstream repository and workbench\" '\n+\t\trm -rf upstream.git workbench &&\n+\t\tgit init --bare upstream.git &&\n+\t\tgit init workbench &&\n+\t\tcreate_commits_in workbench A B &&\n+\t\t(\n+\t\t\tcd workbench &&\n+\t\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\t\tgit config core.abbrev 7 &&\n+\t\t\tgit remote add origin ../upstream.git &&\n+\t\t\tgit update-ref refs/heads/master $A &&\n+\t\t\tgit push origin \\\n+\t\t\t\t$B:refs/heads/master \\\n+\t\t\t\t$A:refs/heads/foo \\\n+\t\t\t\t$A:refs/heads/bar \\\n+\t\t\t\t$A:refs/heads/baz\n+\t\t) &&\n+\t\tgit -C \"workbench\" config advice.pushUpdateRejected false &&\n+\t\tupstream=upstream.git\n+\t'\n+}\n+\n+run_git_push_porcelain_output_test() {\n+\tcase $1 in\n+\thttp)\n+\t\tPROTOCOL=\"HTTP protocol\"\n+\t\tURL_PREFIX=\"http://.*\"\n+\t\t;;\n+\tfile)\n+\t\tPROTOCOL=\"builtin protocol\"\n+\t\tURL_PREFIX=\"\\.\\.\"\n+\t\t;;\n+\tesac\n+\n+\t# Refs of upstream : master(B)  foo(A)  bar(A)  baz(A)\n+\t# Refs of workbench: master(A)                  baz(A)  next(A)\n+\t# git-push         : master(A)  NULL    (B)     baz(A)  next(A)\n+\ttest_expect_success \"porcelain output of successful git-push ($PROTOCOL)\" '\n+\t\t(\n+\t\t\tcd workbench &&\n+\t\t\tgit update-ref refs/heads/master $A &&\n+\t\t\tgit update-ref refs/heads/baz $A &&\n+\t\t\tgit update-ref refs/heads/next $A &&\n+\t\t\tgit push --porcelain --force origin \\\n+\t\t\t\tmaster \\\n+\t\t\t\t:refs/heads/foo \\\n+\t\t\t\t$B:bar \\\n+\t\t\t\tbaz \\\n+\t\t\t\tnext\n+\t\t) >out &&\n+\t\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\tTo <URL/of/upstream.git>\n+\t\t=    refs/heads/baz:refs/heads/baz    [up to date]\n+\t\t     <COMMIT-B>:refs/heads/bar    <OID-A>..<OID-B>\n+\t\t-    :refs/heads/foo    [deleted]\n+\t\t+    refs/heads/master:refs/heads/master    <OID-B>...<OID-A> (forced update)\n+\t\t*    refs/heads/next:refs/heads/next    [new branch]\n+\t\tDone\n+\t\tEOF\n+\t\ttest_cmp expect actual &&\n+\n+\t\tgit -C \"$upstream\" show-ref >out &&\n+\t\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\t<COMMIT-B> refs/heads/bar\n+\t\t<COMMIT-A> refs/heads/baz\n+\t\t<COMMIT-A> refs/heads/master\n+\t\t<COMMIT-A> refs/heads/next\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\t# Refs of upstream : master(A)  bar(B)  baz(A)  next(A)\n+\t# Refs of workbench: master(B)  bar(A)  baz(A)  next(A)\n+\t# git-push         : master(B)  bar(A)  NULL    next(A)\n+\ttest_expect_success \"atomic push failed ($PROTOCOL)\" '\n+\t\t(\n+\t\t\tcd workbench &&\n+\t\t\tgit update-ref refs/heads/master $B &&\n+\t\t\tgit update-ref refs/heads/bar $A &&\n+\t\t\ttest_must_fail git push --atomic --porcelain origin \\\n+\t\t\t\tmaster \\\n+\t\t\t\tbar \\\n+\t\t\t\t:baz \\\n+\t\t\t\tnext\n+\t\t) >out &&\n+\t\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\tTo <URL/of/upstream.git>\n+\t\t!    refs/heads/bar:refs/heads/bar    [rejected] (non-fast-forward)\n+\t\t!    (delete):refs/heads/baz    [rejected] (atomic push failed)\n+\t\t!    refs/heads/master:refs/heads/master    [rejected] (atomic push failed)\n+\t\t!    refs/heads/next:refs/heads/next    [rejected] (atomic push failed)\n+\t\tDone\n+\t\tEOF\n+\t\ttest_cmp expect actual &&\n+\n+\t\tgit -C \"$upstream\" show-ref >out &&\n+\t\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\t<COMMIT-B> refs/heads/bar\n+\t\t<COMMIT-A> refs/heads/baz\n+\t\t<COMMIT-A> refs/heads/master\n+\t\t<COMMIT-A> refs/heads/next\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"prepare pre-receive hook ($PROTOCOL)\" '\n+\t\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n+\t\texit 1\n+\t\tEOF\n+\t'\n+\n+\t# Refs of upstream : master(A)  bar(B)  baz(A)  next(A)\n+\t# Refs of workbench: master(B)  bar(A)  baz(A)  next(A)\n+\t# git-push         : master(B)  bar(A)  NULL    next(A)\n+\ttest_expect_success \"pre-receive hook declined ($PROTOCOL)\" '\n+\t\t(\n+\t\t\tcd workbench &&\n+\t\t\tgit update-ref refs/heads/master $B &&\n+\t\t\tgit update-ref refs/heads/bar $A &&\n+\t\t\ttest_must_fail git push --porcelain --force origin \\\n+\t\t\t\tmaster \\\n+\t\t\t\tbar \\\n+\t\t\t\t:baz \\\n+\t\t\t\tnext\n+\t\t) >out &&\n+\t\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\tTo <URL/of/upstream.git>\n+\t\t=    refs/heads/next:refs/heads/next    [up to date]\n+\t\t!    refs/heads/bar:refs/heads/bar    [remote rejected] (pre-receive hook declined)\n+\t\t!    :refs/heads/baz    [remote rejected] (pre-receive hook declined)\n+\t\t!    refs/heads/master:refs/heads/master    [remote rejected] (pre-receive hook declined)\n+\t\tDone\n+\t\tEOF\n+\t\ttest_cmp expect actual &&\n+\n+\t\tgit -C \"$upstream\" show-ref >out &&\n+\t\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\t<COMMIT-B> refs/heads/bar\n+\t\t<COMMIT-A> refs/heads/baz\n+\t\t<COMMIT-A> refs/heads/master\n+\t\t<COMMIT-A> refs/heads/next\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"remove pre-receive hook ($PROTOCOL)\" '\n+\t\trm \"$upstream/hooks/pre-receive\"\n+\t'\n+\n+\t# Refs of upstream : master(A)  bar(B)  baz(A)  next(A)\n+\t# Refs of workbench: master(B)  bar(A)  baz(A)  next(A)\n+\t# git-push         : master(B)  bar(A)  NULL    next(A)\n+\ttest_expect_success \"non-fastforward push ($PROTOCOL)\" '\n+\t\t(\n+\t\t\tcd workbench &&\n+\t\t\ttest_must_fail git push --porcelain origin \\\n+\t\t\t\tmaster \\\n+\t\t\t\tbar \\\n+\t\t\t\t:baz \\\n+\t\t\t\tnext\n+\t\t) >out &&\n+\t\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\tTo <URL/of/upstream.git>\n+\t\t=    refs/heads/next:refs/heads/next    [up to date]\n+\t\t-    :refs/heads/baz    [deleted]\n+\t\t     refs/heads/master:refs/heads/master    <OID-A>..<OID-B>\n+\t\t!    refs/heads/bar:refs/heads/bar    [rejected] (non-fast-forward)\n+\t\tDone\n+\t\tEOF\n+\t\ttest_cmp expect actual &&\n+\n+\t\tgit -C \"$upstream\" show-ref >out &&\n+\t\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\t<COMMIT-B> refs/heads/bar\n+\t\t<COMMIT-B> refs/heads/master\n+\t\t<COMMIT-A> refs/heads/next\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t'\n+}\n+\n+# Initialize the upstream repository and local workbench.\n+setup_upstream_and_workbench\n+\n+# Run git-push porcelain test on builtin protocol\n+run_git_push_porcelain_output_test file\n+\n+ROOT_PATH=\"$PWD\"\n+. \"$TEST_DIRECTORY\"/lib-gpg.sh\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+. \"$TEST_DIRECTORY\"/lib-terminal.sh\n+start_httpd\n+\n+# Re-initialize the upstream repository and local workbench.\n+setup_upstream_and_workbench\n+\n+test_expect_success \"setup for http\" '\n+\tgit -C upstream.git config http.receivepack true &&\n+\tupstream=\"$HTTPD_DOCUMENT_ROOT_PATH/upstream.git\" &&\n+\tmv upstream.git \"$upstream\" &&\n+\n+\tgit -C workbench remote set-url origin $HTTPD_URL/smart/upstream.git\n+'\n+\n+setup_askpass_helper\n+\n+# Run git-push porcelain test on HTTP protocol\n+run_git_push_porcelain_output_test http\n+\n+test_done\ndiff --git a/transport.c b/transport.c\nindex 1fdc7dac1a..13d638d5fe 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -715,7 +715,15 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re\n \n \tclose(data->fd[1]);\n \tclose(data->fd[0]);\n-\tret |= finish_connect(data->conn);\n+\t/*\n+\t * Atomic push may abort the connection early and close the pipe,\n+\t * which may cause an error for `finish_connect()`. Ignore this error\n+\t * for atomic git-push.\n+\t */\n+\tif (ret || args.atomic)\n+\t\tfinish_connect(data->conn);\n+\telse\n+\t\tret = finish_connect(data->conn);\n \tdata->conn = NULL;\n \tdata->got_remote_heads = 0;\n \n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395654","messageId":"20200417094536.9463-5-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200416162415.5751-1-worldhello.net@gmail.com","subject":"[PATCH v4 4/5] transport-helper: mark failure for atomic push","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-17T09:45:35Z","receivedAt":"2020-04-17T09:45:48Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nCommit v2.22.0-1-g3bca1e7f9f (transport-helper: enforce atomic in\npush_refs_with_push, 2019-07-11) noticed the incomplete report of\nfailure of an atomic push for HTTP protocol.  But the implementation\nhas a flaw that mark all remote references as failure.\n\nOnly mark necessary references as failure in `push_refs_with_push()` of\ntransport-helper.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5541-http-push-smart.sh | 14 +++++++++++---\n t/t5548-push-porcelain.sh  |  5 ++---\n transport-helper.c         | 15 +++++++++++++++\n 3 files changed, 28 insertions(+), 6 deletions(-)\n\ndiff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh\nindex 2c2c3fb0f5..afc680d5e3 100755\n--- a/t/t5541-http-push-smart.sh\n+++ b/t/t5541-http-push-smart.sh\n@@ -177,7 +177,10 @@ test_expect_success 'push (chunked)' '\n \t test $HEAD = $(git rev-parse --verify HEAD))\n '\n \n-test_expect_failure 'push --atomic also prevents branch creation, reports collateral' '\n+## References of remote: atomic1(1)            master(2) collateral(2) other(2)\n+## References of local :            atomic2(2) master(1) collateral(3) other(2) collateral1(3) atomic(1)\n+## Atomic push         :                       master(1) collateral(3)                         atomic(1)\n+test_expect_success 'push --atomic also prevents branch creation, reports collateral' '\n \t# Setup upstream repo - empty for now\n \td=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&\n \tgit init --bare \"$d\" &&\n@@ -189,7 +192,8 @@ test_expect_failure 'push --atomic also prevents branch creation, reports collat\n \ttest_commit atomic2 &&\n \tgit branch collateral &&\n \tgit branch other &&\n-\tgit push \"$up\" master collateral other &&\n+\tgit push \"$up\" atomic1 master collateral other &&\n+\tgit tag -d atomic1 &&\n \n \t# collateral is a valid push, but should be failed by atomic push\n \tgit checkout collateral &&\n@@ -224,7 +228,11 @@ test_expect_failure 'push --atomic also prevents branch creation, reports collat\n \n \t# the collateral failure refs should be indicated to the user\n \tgrep \"^ ! .*rejected.* atomic -> atomic .*atomic push failed\" output &&\n-\tgrep \"^ ! .*rejected.* collateral -> collateral .*atomic push failed\" output\n+\tgrep \"^ ! .*rejected.* collateral -> collateral .*atomic push failed\" output &&\n+\n+\t# never report what we do not push\n+\t! grep \"^ ! .*rejected.* atomic1 \" output &&\n+\t! grep \"^ ! .*rejected.* other \" output\n '\n \n test_expect_success 'push --atomic fails on server-side errors' '\ndiff --git a/t/t5548-push-porcelain.sh b/t/t5548-push-porcelain.sh\nindex 9f4b7de74b..1b19b3ef55 100755\n--- a/t/t5548-push-porcelain.sh\n+++ b/t/t5548-push-porcelain.sh\n@@ -136,7 +136,7 @@ run_git_push_porcelain_output_test() {\n \t# Refs of upstream : master(A)  bar(B)  baz(A)  next(A)\n \t# Refs of workbench: master(B)  bar(A)  baz(A)  next(A)\n \t# git-push         : master(B)  bar(A)  NULL    next(A)\n-\ttest_expect_failure \"atomic push failed ($PROTOCOL)\" '\n+\ttest_expect_success \"atomic push failed ($PROTOCOL)\" '\n \t\t(\n \t\t\tcd workbench &&\n \t\t\tgit update-ref refs/heads/master $B &&\n@@ -150,10 +150,10 @@ run_git_push_porcelain_output_test() {\n \t\tmake_user_friendly_and_stable_output <out >actual &&\n \t\tcat >expect <<-EOF &&\n \t\tTo <URL/of/upstream.git>\n+\t\t=    refs/heads/next:refs/heads/next    [up to date]\n \t\t!    refs/heads/bar:refs/heads/bar    [rejected] (non-fast-forward)\n \t\t!    (delete):refs/heads/baz    [rejected] (atomic push failed)\n \t\t!    refs/heads/master:refs/heads/master    [rejected] (atomic push failed)\n-\t\t!    refs/heads/next:refs/heads/next    [rejected] (atomic push failed)\n \t\tDone\n \t\tEOF\n \t\ttest_cmp expect actual &&\n@@ -168,7 +168,6 @@ run_git_push_porcelain_output_test() {\n \t\tEOF\n \t\ttest_cmp expect actual\n \t'\n-\n \ttest_expect_success \"prepare pre-receive hook ($PROTOCOL)\" '\n \t\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n \t\texit 1\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 20a7185ec4..ab3b52eb14 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -894,6 +894,21 @@ static int push_refs_with_push(struct transport *transport,\n \t\tcase REF_STATUS_REJECT_STALE:\n \t\tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n \t\t\tif (atomic) {\n+\t\t\t\t/* Mark other refs as failed */\n+\t\t\t\tfor (ref = remote_refs; ref; ref = ref->next) {\n+\t\t\t\t\tif (!ref->peer_ref && !mirror)\n+\t\t\t\t\t\tcontinue;\n+\n+\t\t\t\t\tswitch (ref->status) {\n+\t\t\t\t\tcase REF_STATUS_NONE:\n+\t\t\t\t\tcase REF_STATUS_OK:\n+\t\t\t\t\tcase REF_STATUS_EXPECTING_REPORT:\n+\t\t\t\t\t\tref->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\tdefault:\n+\t\t\t\t\t\tbreak; /* do nothing */\n+\t\t\t\t\t}\n+\t\t\t\t}\n \t\t\t\tstring_list_clear(&cas_options, 0);\n \t\t\t\treturn 0;\n \t\t\t} else\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395655","messageId":"20200417094536.9463-4-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200416162415.5751-1-worldhello.net@gmail.com","subject":"[PATCH v4 3/5] send-pack: mark failure of atomic push properly","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-17T09:45:34Z","receivedAt":"2020-04-17T09:45:48Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing with SSH or other smart protocol, references are validated\nby function `check_to_send_update()` before they are sent in commands\nto `send_pack()` of \"receve-pack\".  For atomic push, if a reference is\nrejected after the validation, only references pushed by user should be\nmarked as failure, instead of report failure on all remote references.\n\nCommit v2.22.0-1-g3bca1e7f9f (transport-helper: enforce atomic in\npush_refs_with_push, 2019-07-11) wanted to fix report issue of HTTP\nprotocol, but marked all remote references failure for atomic push.\n\nIn order to fix the issue of status report for SSH or other built-in\nsmart protocol, revert part of that commit and add additional status\nfor function `atomic_push_failure()`.  The additional status for it\nexcept the \"REF_STATUS_EXPECTING_REPORT\" status are:\n\n- REF_STATUS_NONE : Not marked as \"REF_STATUS_EXPECTING_REPORT\" yet.\n- REF_STATUS_OK   : Assume OK for dryrun or status_report is disabled.\n\nThis fix won't resolve the issue of status report in transport-helper\nfor HTTP or other protocols, and breaks test case in t5541.  Will fix\nit in additional commit.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n send-pack.c                |  2 ++\n t/t5541-http-push-smart.sh |  2 +-\n t/t5543-atomic-push.sh     |  6 +++---\n t/t5548-push-porcelain.sh  |  2 +-\n transport.c                | 14 --------------\n 5 files changed, 7 insertions(+), 19 deletions(-)\n\ndiff --git a/send-pack.c b/send-pack.c\nindex 1835cd5582..efefb687b2 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -332,6 +332,8 @@ static int atomic_push_failure(struct send_pack_args *args,\n \t\t\tcontinue;\n \n \t\tswitch (ref->status) {\n+\t\tcase REF_STATUS_NONE:\n+\t\tcase REF_STATUS_OK:\n \t\tcase REF_STATUS_EXPECTING_REPORT:\n \t\t\tref->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n \t\t\tcontinue;\ndiff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh\nindex 23be8ce92d..2c2c3fb0f5 100755\n--- a/t/t5541-http-push-smart.sh\n+++ b/t/t5541-http-push-smart.sh\n@@ -177,7 +177,7 @@ test_expect_success 'push (chunked)' '\n \t test $HEAD = $(git rev-parse --verify HEAD))\n '\n \n-test_expect_success 'push --atomic also prevents branch creation, reports collateral' '\n+test_expect_failure 'push --atomic also prevents branch creation, reports collateral' '\n \t# Setup upstream repo - empty for now\n \td=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&\n \tgit init --bare \"$d\" &&\ndiff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh\nindex 001240eec7..620c30d58f 100755\n--- a/t/t5543-atomic-push.sh\n+++ b/t/t5543-atomic-push.sh\n@@ -200,7 +200,7 @@ test_expect_success 'atomic push is not advertised if configured' '\n # References in upstream : master(1) one(1) foo(1)\n # References in workbench: master(2)        foo(1) two(2) bar(2)\n # Atomic push            : master(2)               two(2) bar(2)\n-test_expect_failure 'atomic push reports (reject by update hook)' '\n+test_expect_success 'atomic push reports (reject by update hook)' '\n \tmk_repo_pair &&\n \t(\n \t\tcd workbench &&\n@@ -241,7 +241,7 @@ test_expect_failure 'atomic push reports (reject by update hook)' '\n \n # References in upstream : master(1) one(1) foo(1)\n # References in workbench: master(2)        foo(1) two(2) bar(2)\n-test_expect_failure 'atomic push reports (mirror, but reject by update hook)' '\n+test_expect_success 'atomic push reports (mirror, but reject by update hook)' '\n \t(\n \t\tcd workbench &&\n \t\tgit remote remove up &&\n@@ -262,7 +262,7 @@ test_expect_failure 'atomic push reports (mirror, but reject by update hook)' '\n \n # References in upstream : master(2) one(1) foo(1)\n # References in workbench: master(1)        foo(1) two(2) bar(2)\n-test_expect_failure 'atomic push reports (reject by non-ff)' '\n+test_expect_success 'atomic push reports (reject by non-ff)' '\n \trm upstream/.git/hooks/update &&\n \t(\n \t\tcd workbench &&\ndiff --git a/t/t5548-push-porcelain.sh b/t/t5548-push-porcelain.sh\nindex 96ba449a38..9f4b7de74b 100755\n--- a/t/t5548-push-porcelain.sh\n+++ b/t/t5548-push-porcelain.sh\n@@ -136,7 +136,7 @@ run_git_push_porcelain_output_test() {\n \t# Refs of upstream : master(A)  bar(B)  baz(A)  next(A)\n \t# Refs of workbench: master(B)  bar(A)  baz(A)  next(A)\n \t# git-push         : master(B)  bar(A)  NULL    next(A)\n-\ttest_expect_success \"atomic push failed ($PROTOCOL)\" '\n+\ttest_expect_failure \"atomic push failed ($PROTOCOL)\" '\n \t\t(\n \t\t\tcd workbench &&\n \t\t\tgit update-ref refs/heads/master $B &&\ndiff --git a/transport.c b/transport.c\nindex 13d638d5fe..a07e39564c 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1248,20 +1248,6 @@ int transport_push(struct repository *r,\n \t\terr = push_had_errors(remote_refs);\n \t\tret = push_ret | err;\n \n-\t\tif ((flags & TRANSPORT_PUSH_ATOMIC) && err) {\n-\t\t\tstruct ref *it;\n-\t\t\tfor (it = remote_refs; it; it = it->next)\n-\t\t\t\tswitch (it->status) {\n-\t\t\t\tcase REF_STATUS_NONE:\n-\t\t\t\tcase REF_STATUS_UPTODATE:\n-\t\t\t\tcase REF_STATUS_OK:\n-\t\t\t\t\tit->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n-\t\t\t\t\tbreak;\n-\t\t\t\tdefault:\n-\t\t\t\t\tbreak;\n-\t\t\t\t}\n-\t\t}\n-\n \t\tif (!quiet || err)\n \t\t\ttransport_print_push_status(transport->url, remote_refs,\n \t\t\t\t\tverbose | porcelain, porcelain,\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395656","messageId":"20200417094536.9463-6-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200416162415.5751-1-worldhello.net@gmail.com","subject":"[PATCH v4 5/5] transport-helper: new method reject_atomic_push()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-17T09:45:36Z","receivedAt":"2020-04-17T09:45:50Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd new method in transport-helper to reject all references if any\nreference is failed for atomic push.\n\nThis method is reused in \"send-pack.c\" and \"transport-helper.c\", one for\nSSH, git and file protocols, and the other for HTTP protocol.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n send-pack.c        | 29 +++--------------------------\n transport-helper.c | 38 +++++++++++++++++++++++---------------\n transport.h        |  3 +++\n 3 files changed, 29 insertions(+), 41 deletions(-)\n\ndiff --git a/send-pack.c b/send-pack.c\nindex efefb687b2..a7c53193c9 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -320,31 +320,6 @@ static int generate_push_cert(struct strbuf *req_buf,\n \treturn update_seen;\n }\n \n-\n-static int atomic_push_failure(struct send_pack_args *args,\n-\t\t\t       struct ref *remote_refs,\n-\t\t\t       struct ref *failing_ref)\n-{\n-\tstruct ref *ref;\n-\t/* Mark other refs as failed */\n-\tfor (ref = remote_refs; ref; ref = ref->next) {\n-\t\tif (!ref->peer_ref && !args->send_mirror)\n-\t\t\tcontinue;\n-\n-\t\tswitch (ref->status) {\n-\t\tcase REF_STATUS_NONE:\n-\t\tcase REF_STATUS_OK:\n-\t\tcase REF_STATUS_EXPECTING_REPORT:\n-\t\t\tref->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n-\t\t\tcontinue;\n-\t\tdefault:\n-\t\t\tbreak; /* do nothing */\n-\t\t}\n-\t}\n-\treturn error(\"atomic push failed for ref %s. status: %d\\n\",\n-\t\t     failing_ref->name, failing_ref->status);\n-}\n-\n #define NONCE_LEN_LIMIT 256\n \n static void reject_invalid_nonce(const char *nonce, int len)\n@@ -489,7 +464,9 @@ int send_pack(struct send_pack_args *args,\n \t\t\tif (use_atomic) {\n \t\t\t\tstrbuf_release(&req_buf);\n \t\t\t\tstrbuf_release(&cap_buf);\n-\t\t\t\tatomic_push_failure(args, remote_refs, ref);\n+\t\t\t\treject_atomic_push(remote_refs, args->send_mirror);\n+\t\t\t\terror(\"atomic push failed for ref %s. status: %d\\n\",\n+\t\t\t\t      ref->name, ref->status);\n \t\t\t\treturn args->porcelain ? 0 : -1;\n \t\t\t}\n \t\t\t/* else fallthrough */\ndiff --git a/transport-helper.c b/transport-helper.c\nindex ab3b52eb14..a46afcb69d 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -894,21 +894,7 @@ static int push_refs_with_push(struct transport *transport,\n \t\tcase REF_STATUS_REJECT_STALE:\n \t\tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n \t\t\tif (atomic) {\n-\t\t\t\t/* Mark other refs as failed */\n-\t\t\t\tfor (ref = remote_refs; ref; ref = ref->next) {\n-\t\t\t\t\tif (!ref->peer_ref && !mirror)\n-\t\t\t\t\t\tcontinue;\n-\n-\t\t\t\t\tswitch (ref->status) {\n-\t\t\t\t\tcase REF_STATUS_NONE:\n-\t\t\t\t\tcase REF_STATUS_OK:\n-\t\t\t\t\tcase REF_STATUS_EXPECTING_REPORT:\n-\t\t\t\t\t\tref->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n-\t\t\t\t\t\tcontinue;\n-\t\t\t\t\tdefault:\n-\t\t\t\t\t\tbreak; /* do nothing */\n-\t\t\t\t\t}\n-\t\t\t\t}\n+\t\t\t\treject_atomic_push(remote_refs, mirror);\n \t\t\t\tstring_list_clear(&cas_options, 0);\n \t\t\t\treturn 0;\n \t\t\t} else\n@@ -1503,3 +1489,25 @@ int bidirectional_transfer_loop(int input, int output)\n \n \treturn tloop_spawnwait_tasks(&state);\n }\n+\n+void reject_atomic_push(struct ref *remote_refs, int mirror_mode)\n+{\n+\tstruct ref *ref;\n+\n+\t/* Mark other refs as failed */\n+\tfor (ref = remote_refs; ref; ref = ref->next) {\n+\t\tif (!ref->peer_ref && !mirror_mode)\n+\t\t\tcontinue;\n+\n+\t\tswitch (ref->status) {\n+\t\tcase REF_STATUS_NONE:\n+\t\tcase REF_STATUS_OK:\n+\t\tcase REF_STATUS_EXPECTING_REPORT:\n+\t\t\tref->status = REF_STATUS_ATOMIC_PUSH_FAILED;\n+\t\t\tcontinue;\n+\t\tdefault:\n+\t\t\tbreak; /* do nothing */\n+\t\t}\n+\t}\n+\treturn;\n+}\ndiff --git a/transport.h b/transport.h\nindex e0131daab9..4298c855be 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -265,4 +265,7 @@ int transport_refs_pushed(struct ref *ref);\n void transport_print_push_status(const char *dest, struct ref *refs,\n \t\t  int verbose, int porcelain, unsigned int *reject_reasons);\n \n+/* common method used by transport-helper.c and send-pack.c */\n+void reject_atomic_push(struct ref *refs, int mirror_mode);\n+\n #endif\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395667","messageId":"xmqqlfmtvqkh.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200417094536.9463-2-worldhello.net@gmail.com","subject":"Re: [PATCH v4 1/5] send-pack: fix inconsistent porcelain output","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-04-17T19:51:26Z","receivedAt":"2020-04-17T19:51:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> diff --git a/transport.c b/transport.c\n> index 1fdc7dac1a..13d638d5fe 100644\n> --- a/transport.c\n> +++ b/transport.c\n> @@ -715,7 +715,15 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re\n>  \n>  \tclose(data->fd[1]);\n>  \tclose(data->fd[0]);\n> -\tret |= finish_connect(data->conn);\n> +\t/*\n> +\t * Atomic push may abort the connection early and close the pipe,\n> +\t * which may cause an error for `finish_connect()`. Ignore this error\n> +\t * for atomic git-push.\n> +\t */\n> +\tif (ret || args.atomic)\n> +\t\tfinish_connect(data->conn);\n> +\telse\n> +\t\tret = finish_connect(data->conn);\n\nWhich means that the return value from this function under the\natomic mode is what we got from an earlier call to send_pack().\n\nWhich may have failed, in which case we do want to relay the error\nto our caller, or may have succeeded, which is also fine to relay to\nour caller.\n\n> The following code at the end of function `send_pack()` indicates that\n> `send_pack()` should not return an error if some references are rejected\n> in porcelain mode.\n\nUnder porcelain mode, if we already saw an error before we get to\nthe \"if porcelain, do not bother checking the remote status\", we\ndo relay the error to our caller.  That happens in the part you\nomitted from your quote:\n\n>     int send_pack(...)\n>         ... ...\n\n\tif (ret < 0)\n\t\treturn ret;\n\n>\n>         if (args->porcelain)\n>             return 0;\n>\n>         for (ref = remote_refs; ref; ref = ref->next) {\n>             switch (ref->status) {\n>             case REF_STATUS_NONE:\n>             case REF_STATUS_UPTODATE:\n>             case REF_STATUS_OK:\n>                 break;\n>             default:\n>                 return -1;\n>             }\n>         }\n>         return 0;\n>     }\n\nSo it is not like we are disabling all errors under the porcelain\nmode.\n\n> diff --git a/send-pack.c b/send-pack.c\n> index 0407841ae8..1835cd5582 100644\n> --- a/send-pack.c\n> +++ b/send-pack.c\n> @@ -489,7 +487,8 @@ int send_pack(struct send_pack_args *args,\n>  \t\t\tif (use_atomic) {\n>  \t\t\t\tstrbuf_release(&req_buf);\n>  \t\t\t\tstrbuf_release(&cap_buf);\n> -\t\t\t\treturn atomic_push_failure(args, remote_refs, ref);\n> +\t\t\t\tatomic_push_failure(args, remote_refs, ref);\n> +\t\t\t\treturn args->porcelain ? 0 : -1;\n>  \t\t\t}\n\nAnd this is in line with the way how rejected pushes are handled, I guess.\n\nSounds a bit convoluted but correct ;-)\n"},{"id":"395683","messageId":"20200418160334.15631-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200414123257.27449-1-worldhello.net@gmail.com","subject":"[PATCH v13 1/8] transport: not report a non-head push as a branch","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-18T16:03:27Z","receivedAt":"2020-04-18T16:03:53Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing a new reference (not a head or tag), report it as a new\nreference instead of a new branch.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411-proc-receive-hook.sh                  | 105 ++++++++++++++++++\n t/t5411/common-functions.sh                   |  56 ++++++++++\n t/t5411/test-0000-standard-git-push.sh        |  84 ++++++++++++++\n .../test-0001-standard-git-push--porcelain.sh |  87 +++++++++++++++\n t/t5411/test-0002-pre-receive-declined.sh     |  33 ++++++\n ...st-0003-pre-receive-declined--porcelain.sh |  34 ++++++\n t/t5516-fetch-push.sh                         |   2 +-\n transport.c                                   |   9 +-\n 8 files changed, 406 insertions(+), 4 deletions(-)\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/test-0000-standard-git-push.sh\n create mode 100644 t/t5411/test-0001-standard-git-push--porcelain.sh\n create mode 100644 t/t5411/test-0002-pre-receive-declined.sh\n create mode 100644 t/t5411/test-0003-pre-receive-declined--porcelain.sh\n\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nnew file mode 100755\nindex 0000000000..33d4e019ae\n--- /dev/null\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -0,0 +1,105 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook'\n+\n+. ./test-lib.sh\n+\n+. \"$TEST_DIRECTORY\"/t5411/common-functions.sh\n+\n+setup_upstream_and_workbench () {\n+\t# Refs of upstream : master(A)\n+\t# Refs of workbench: master(A)  tags/v123\n+\ttest_expect_success \"setup upstream and workbench\" '\n+\t\trm -rf upstream.git &&\n+\t\trm -rf workbench &&\n+\t\tgit init --bare upstream.git &&\n+\t\tgit init workbench &&\n+\t\tcreate_commits_in workbench A B &&\n+\t\t(\n+\t\t\tcd workbench &&\n+\t\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\t\tgit config core.abbrev 7 &&\n+\t\t\tgit remote add origin ../upstream.git &&\n+\t\t\tgit update-ref refs/heads/master $A &&\n+\t\t\tgit tag -m \"v123\" v123 $A &&\n+\t\t\tgit push origin $A:refs/heads/master\n+\t\t) &&\n+\t\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t\t# setup pre-receive hook\n+\t\twrite_script upstream.git/hooks/pre-receive <<-\\EOF &&\n+\t\texec >&2\n+\t\techo \"# pre-receive hook\"\n+\t\twhile read old new ref\n+\t\tdo\n+\t\t\techo \"pre-receive< $old $new $ref\"\n+\t\tdone\n+\t\tEOF\n+\n+\t\t# setup post-receive hook\n+\t\twrite_script upstream.git/hooks/post-receive <<-\\EOF &&\n+\t\texec >&2\n+\t\techo \"# post-receive hook\"\n+\t\twhile read old new ref\n+\t\tdo\n+\t\t\techo \"post-receive< $old $new $ref\"\n+\t\tdone\n+\t\tEOF\n+\n+\t\tupstream=upstream.git\n+\t'\n+}\n+\n+run_proc_receive_hook_test() {\n+\tcase $1 in\n+\thttp)\n+\t\tPROTOCOL=\"HTTP protocol\"\n+\t\tURL_PREFIX=\"http://.*\"\n+\t\t;;\n+\tfile | local)\n+\t\tPROTOCOL=\"builtin protocol\"\n+\t\tURL_PREFIX=\"\\.\\.\"\n+\t\t;;\n+\tesac\n+\n+\t# Include test cases for both file and HTTP protocol\n+\tfor t in  \"$TEST_DIRECTORY\"/t5411/test-*.sh\n+\tdo\n+\t\t. \"$t\"\n+\tdone\n+}\n+\n+# Initialize the upstream repository and local workbench.\n+setup_upstream_and_workbench\n+\n+# Run test cases for 'proc-receive' hook on local file protocol.\n+run_proc_receive_hook_test local\n+\n+ROOT_PATH=\"$PWD\"\n+. \"$TEST_DIRECTORY\"/lib-gpg.sh\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+. \"$TEST_DIRECTORY\"/lib-terminal.sh\n+start_httpd\n+\n+# Re-initialize the upstream repository and local workbench.\n+setup_upstream_and_workbench\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup for HTTP protocol\" '\n+\tgit -C upstream.git config http.receivepack true &&\n+\tupstream=\"$HTTPD_DOCUMENT_ROOT_PATH/upstream.git\" &&\n+\tmv upstream.git \"$upstream\" &&\n+\tgit -C workbench remote set-url origin $HTTPD_URL/smart/upstream.git\n+'\n+\n+setup_askpass_helper\n+\n+# Run test cases for 'proc-receive' hook on HTTP protocol.\n+run_proc_receive_hook_test http\n+\n+test_done\ndiff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\nnew file mode 100644\nindex 0000000000..6580bebd8e\n--- /dev/null\n+++ b/t/t5411/common-functions.sh\n@@ -0,0 +1,56 @@\n+# Create commits in <repo> and assign each commit's oid to shell variables\n+# given in the arguments (A, B, and C). E.g.:\n+#\n+#     create_commits_in <repo> A B C\n+#\n+# NOTE: Never calling this function from a subshell since variable\n+# assignments will disappear when subshell exits.\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{} --)\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  We can easily prepare the expect text\n+# without having to worry about future changes of the commit ID and spaces\n+# of the output.  Single quotes are replaced with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect text.  We also\n+# remove some locale error messages, which break test if we turn on\n+# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n+# on plumbing commands.\n+make_user_friendly_and_stable_output () {\n+\tsed \\\n+\t\t-e \"s/  *\\$//\" \\\n+\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/'/\\\"/g\" \\\n+\t\t-e \"s/\t/    /g\" \\\n+\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n+\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n+\t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n+\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n+\t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n+\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\" \\\n+\t\t-e \"s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#\" \\\n+\t\t-e \"/^error: / d\"\n+}\ndiff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh\nnew file mode 100644\nindex 0000000000..2ee35d17b1\n--- /dev/null\n+++ b/t/t5411/test-0000-standard-git-push.sh\n@@ -0,0 +1,84 @@\n+# Refs of upstream : master(A)  \n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"non-force git-push ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t * [new branch] HEAD -> next\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"force git-push ($PROTOCOL)\" '\n+\tgit -C workbench push -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tHEAD:refs/heads/master \\\n+\t\tHEAD:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo <URL/of/upstream.git>\n+\t + <OID-B>...<OID-A> HEAD -> master (forced update)\n+\t - [deleted] next\n+\t * [new tag] v123 -> v123\n+\t * [new reference] HEAD -> refs/review/master/topic\n+\t * [new branch] HEAD -> a/b/c\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\ndiff --git a/t/t5411/test-0001-standard-git-push--porcelain.sh b/t/t5411/test-0001-standard-git-push--porcelain.sh\nnew file mode 100644\nindex 0000000000..6b7ad15543\n--- /dev/null\n+++ b/t/t5411/test-0001-standard-git-push--porcelain.sh\n@@ -0,0 +1,87 @@\n+# Refs of upstream : master(A)  \n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"non-force git-push (--porcelain, $PROTOCOL)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"force git-push (--porcelain, $PROTOCOL)\" '\n+\tgit -C workbench push --porcelain -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tHEAD:refs/heads/master \\\n+\t\tHEAD:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo <URL/of/upstream.git>\n+\t+    HEAD:refs/heads/master    <OID-B>...<OID-A> (forced update)\n+\t-    :refs/heads/next    [deleted]\n+\t*    refs/tags/v123:refs/tags/v123    [new tag]\n+\t*    HEAD:refs/review/master/topic    [new reference]\n+\t*    HEAD:refs/heads/a/b/c    [new branch]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\n+\ndiff --git a/t/t5411/test-0002-pre-receive-declined.sh b/t/t5411/test-0002-pre-receive-declined.sh\nnew file mode 100644\nindex 0000000000..71203fbfd5\n--- /dev/null\n+++ b/t/t5411/test-0002-pre-receive-declined.sh\n@@ -0,0 +1,33 @@\n+test_expect_success \"setup pre-receive hook (declined, $PROTOCOL)\" '\n+\tmv \"$upstream/hooks/pre-receive\" \"$upstream/hooks/pre-receive.ok\" &&\n+\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n+\texit 1\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push is declined ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] <COMMIT-B> -> master (pre-receive hook declined)\n+\t ! [remote rejected] HEAD -> next (pre-receive hook declined)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tmv \"$upstream/hooks/pre-receive.ok\" \"$upstream/hooks/pre-receive\"\n+'\ndiff --git a/t/t5411/test-0003-pre-receive-declined--porcelain.sh b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\nnew file mode 100644\nindex 0000000000..0bf4e44657\n--- /dev/null\n+++ b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\n@@ -0,0 +1,34 @@\n+test_expect_success \"setup pre-receive hook (declined, $PROTOCOL)\" '\n+\tmv \"$upstream/hooks/pre-receive\" \"$upstream/hooks/pre-receive.ok\" &&\n+\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n+\texit 1\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push is declined (--porcelain, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    <COMMIT-B>:refs/heads/master    [remote rejected] (pre-receive hook declined)\n+\t!    HEAD:refs/heads/next    [remote rejected] (pre-receive hook declined)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tmv \"$upstream/hooks/pre-receive.ok\" \"$upstream/hooks/pre-receive\"\n+'\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 9c6218f568..77938db77f 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1039,7 +1039,7 @@ test_force_fetch_tag \"annotated tag\" \"-f -a -m'tag message'\"\n test_expect_success 'push --porcelain' '\n \tmk_empty testrepo &&\n \techo >.git/foo  \"To testrepo\" &&\n-\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new branch]\"  &&\n+\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new reference]\"  &&\n \techo >>.git/foo \"Done\" &&\n \tgit push >.git/bar --porcelain  testrepo refs/heads/master:refs/remotes/origin/master &&\n \t(\ndiff --git a/transport.c b/transport.c\nindex a07e39564c..73defc4a5a 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -500,9 +500,12 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\t\t\t porcelain, summary_width);\n \telse if (is_null_oid(&ref->old_oid))\n \t\tprint_ref_status('*',\n-\t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n-\t\t\t\"[new branch]\"),\n-\t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t  ? \"[new tag]\"\n+\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t     ? \"[new branch]\"\n+\t\t\t\t     : \"[new reference]\")),\n+\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395690","messageId":"20200418160334.15631-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200414123257.27449-1-worldhello.net@gmail.com","subject":"[PATCH v13 0/8] New proc-receive hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-18T16:03:26Z","receivedAt":"2020-04-18T16:03:54Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nNote: This patches series is based on topic branch `jx/atomic-push`,\n      for there are overlap changes between these two topics.\n\n## Changes since v12\n\n1. test: Remove t5412, and move HTTP test cases into t5411.\n\n2. test: Split test cases into multiple files as `t/t5411/test-*.sh`.\n\n3. test: Add test cases for porcelain `git-push` output for\n   `pro-receive` hook.  See files 't/t5411/test-*--porcelain.sh'.\n\n4. New patch 5/8 to feed proper commands to `post-receive` hook.\n\n5. Use flag `PACKET_READ_GENTLE_ON_EOF` instead of\n   `PACKET_READ_DIE_ON_ERR_PACKET` for `packet_reader_init()`\n   when reading result from the 'proc-receive' hook.\n   So 'receive-pack' won't die with the `proc-receive` hook.\n\n6.  Add two new options (`--die-version` and `--die-readline`) for\n   't/helper/test-proc-receive.c', so we can test unexpected crash of\n   'proc-receive' hook.\n\n7. Apply the \"SQUASH???\" commit.\n\n## Range-diff v12...v13\n\n1:  77a731c151 < -:  ---------- transport: not report a non-head push as a branch\n-:  ---------- > 1:  312ee17e84 transport: not report a non-head push as a branch\n2:  4c4f7cffff = 2:  e86a3c5a4e connect: export parse_feature_value()\n3:  b76900cb0f ! 3:  5adc60e9d1 receive-pack: add new proc-receive hook\n    @@ Makefile: TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n      TEST_BUILTINS_OBJS += test-read-cache.o\n     \n      ## builtin/receive-pack.c ##\n    -@@\n    - #include \"remote.h\"\n    - #include \"connect.h\"\n    - #include \"string-list.h\"\n    -+#include \"string.h\"\n    - #include \"sha1-array.h\"\n    - #include \"connected.h\"\n    - #include \"argv-array.h\"\n     @@ builtin/receive-pack.c: static void write_head_info(void)\n      \tpacket_flush(1);\n      }\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\n     +\tfor (;;) {\n     +\t\tstruct object_id old_oid, new_oid;\n    -+\t\tchar *status;\n    ++\t\tconst char *status;\n     +\t\tchar *refname;\n     +\t\tchar *remains = NULL;\n     +\n     +\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n     +\t\t\tbreak;\n    -+\t\tstatus = (char *)reader->line;\n    ++\t\tstatus = reader->line;\n     +\t\trefname = strchr(status, ' ');\n     +\t\tif (refname) {\n     +\t\t\t*refname++ = '\\0';\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\t\t/* Reset \"run_proc_receive\" field, and continue to run in \"receive-pack\" */\n     +\t\t\thint->run_proc_receive = 0;\n     +\t\t} else if (!strcmp(\"alt\", status)) {\n    -+\t\t\tchar *alt_ref;\n    ++\t\t\tchar *alt_ref = NULL;\n    ++\t\t\tchar *p1, *p2;\n     +\n    -+\t\t\talt_ref = remains;\n    -+\t\t\tremains = strchr(alt_ref, ' ');\n    -+\t\t\tif (remains)\n    -+\t\t\t\t*remains++ = '\\0';\n    ++\t\t\tif (!remains) {\n    ++\t\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'alt <ref> <alt-ref> ...', got 'alt %s'\\n\",\n    ++\t\t\t\t\t    refname);\n    ++\t\t\t\tcontinue;\n    ++\t\t\t}\n    ++\t\t\tp1 = strchr(remains, '=');\n    ++\t\t\tp2 = strchr(remains, ' ');\n    ++\t\t\tif (!p1) {\n    ++\t\t\t\t/*\n    ++\t\t\t\t * \"alt <ref> <alt-ref>\", or\n    ++\t\t\t\t * \"alt <ref> <alt-ref> forced-update\"\n    ++\t\t\t\t */\n    ++\t\t\t\talt_ref = remains;\n    ++\t\t\t\tremains = p2;\n    ++\t\t\t\tif (remains)\n    ++\t\t\t\t\t*remains++ = '\\0';\n    ++\t\t\t} else if (!p2) {\n    ++\t\t\t\t/* \"alt <ref> old-oid=<oid>\" */\n    ++\t\t\t\talt_ref = NULL;\n    ++\t\t\t} else if (p1 > p2) {\n    ++\t\t\t\t/* \"alt <ref> <alt-ref> old-oid=<oid>\" */\n    ++\t\t\t\talt_ref = remains;\n    ++\t\t\t\tremains = p2;\n    ++\t\t\t\tif (remains)\n    ++\t\t\t\t\t*remains++ = '\\0';\n    ++\t\t\t} else {\n    ++\t\t\t\t/* \"alt <ref> old-oid=<oid1> new-oid=<oid2>\" */\n    ++\t\t\t\talt_ref = NULL;\n    ++\t\t\t}\n     +\n     +\t\t\tif (hint->extended_status)\n     +\t\t\t\tstrbuf_addstr(&extended_status, hint->extended_status);\n     +\n    -+\t\t\tstrbuf_addf(&extended_status, \"%sref=%s\",\n    -+\t\t\t\t    extended_status.len > 0 ? \" \": \"\",\n    -+\t\t\t\t    alt_ref);\n    ++\t\t\t/*\n    ++\t\t\t * Already received an \"ok <ref>\" response, add an\n    ++\t\t\t * additional \"ref=<ref>\" field.\n    ++\t\t\t */\n    ++\t\t\tif (extended_status.len == 0 &&\n    ++\t\t\t    hint->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)\n    ++\t\t\t\tstrbuf_addf(&extended_status, \"ref=%s\", refname);\n    ++\n    ++\t\t\tif (alt_ref)\n    ++\t\t\t\tstrbuf_addf(&extended_status, \"%sref=%s\",\n    ++\t\t\t\t\t    extended_status.len > 0 ? \" \": \"\",\n    ++\t\t\t\t\t    alt_ref);\n    ++\t\t\telse if (extended_status.len > 0)\n    ++\t\t\t\t/*\n    ++\t\t\t\t * Start new extended status for refname, if it\n    ++\t\t\t\t * is not the first report for this reference.\n    ++\t\t\t\t */\n    ++\t\t\t\tstrbuf_addf(&extended_status, \" ref=%s\", refname);\n     +\n     +\t\t\tif (remains) {\n     +\t\t\t\tconst char *val;\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t/* Version negotiaton */\n     +\tpacket_reader_init(&reader, proc.out, NULL, 0,\n     +\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n    -+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n    ++\t\t\t   PACKET_READ_GENTLE_ON_EOF);\n     +\tif (use_atomic)\n     +\t\tstrbuf_addstr(&cap, \" atomic\");\n     +\tif (use_push_options)\n    @@ t/helper/test-proc-receive.c (new)\n     +\tNULL\n     +};\n     +\n    -+static int version = 1;\n    -+static int verbose = 0;\n    ++static int die_version = 0;\n    ++static int die_readline = 0;\n     +static int no_push_options = 0;\n     +static int use_atomic = 0;\n     +static int use_push_options = 0;\n    ++static int verbose = 0;\n    ++static int version = 1;\n     +static struct string_list returns = STRING_LIST_INIT_NODUP;\n     +\n     +struct command {\n    @@ t/helper/test-proc-receive.c (new)\n     +\t\t}\n     +\t}\n     +\n    -+\tif (server_version != 1)\n    ++\tif (server_version != 1 || die_version)\n     +\t\tdie(\"bad protocol version: %d\", server_version);\n     +\n     +\tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n    @@ t/helper/test-proc-receive.c (new)\n     +\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n     +\t\t    *p++ != ' ' ||\n     +\t\t    parse_oid_hex(p, &new_oid, &p) ||\n    -+\t\t    *p++ != ' ')\n    ++\t\t    *p++ != ' ' ||\n    ++\t\t    die_readline)\n     +\t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n     +\t\t\t    reader->line);\n     +\t\trefname = p;\n    @@ t/helper/test-proc-receive.c (new)\n     +\tstruct option options[] = {\n     +\t\tOPT_BOOL(0, \"no-push-options\", &no_push_options,\n     +\t\t\t \"disable push options\"),\n    ++\t\tOPT_BOOL(0, \"die-version\", &die_version,\n    ++\t\t\t \"die during version negotiation\"),\n    ++\t\tOPT_BOOL(0, \"die-readline\", &die_readline,\n    ++\t\t\t \"die when readline\"),\n     +\t\tOPT_STRING_LIST('r', \"return\", &returns, \"old/new/ref/status/msg\",\n     +\t\t\t\t\"return of results\"),\n     +\t\tOPT__VERBOSE(&verbose, \"be verbose\"),\n    @@ t/helper/test-proc-receive.c (new)\n     +\t}\n     +\n     +\tif (returns.nr)\n    -+\t\tfor_each_string_list_item(item, &returns) {\n    -+\t\t\tchar *p;\n    -+\n    -+\t\t\tp = strstr(item->string, \"\\\\0\");\n    -+\t\t\tif (p) {\n    -+\t\t\t\t*p = '\\0';\n    -+\t\t\t\tp += 2;\n    -+\t\t\t\tpacket_write_fmt(1, \"%s%c%s\\n\", item->string, '\\0', p);\n    -+\t\t\t} else {\n    -+\t\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n    -+\t\t\t}\n    -+\t\t}\n    ++\t\tfor_each_string_list_item(item, &returns)\n    ++\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n     +\tpacket_flush(1);\n     +\tsigchain_pop(SIGPIPE);\n     +\n    @@ t/helper/test-tool.h: int cmd__parse_pathspec_file(int argc, const char** argv);\n      int cmd__reach(int argc, const char **argv);\n      int cmd__read_cache(int argc, const char **argv);\n     \n    - ## t/t5411/common-functions.sh ##\n    -@@ t/t5411/common-functions.sh: create_commits_in () {\n    - # Format the output of git-push, git-show-ref and other commands to make a\n    - # user-friendly and stable text.  We can easily prepare the expect text\n    - # without having to worry about future changes of the commit ID and spaces\n    --# of the output.  We also replce single quotes with double quotes, because\n    --# it is boring to prepare unquoted single quotes in expect text.\n    -+# of the output.  Single quotes are replaced with double quotes, because\n    -+# it is boring to prepare unquoted single quotes in expect text.  We also\n    -+# remove some locale error messages, which break test if we turn on\n    -+# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n    -+# on plumbing commands.\n    - make_user_friendly_and_stable_output_common () {\n    - \tsed \\\n    - \t\t-e \"s/  *\\$//\" \\\n    -@@ t/t5411/common-functions.sh: make_user_friendly_and_stable_output_common () {\n    - \t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n    - \t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n    - \t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n    --\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\"\n    -+\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\" \\\n    -+\t\t-e \"/^error: / d\"\n    - }\n    -\n    - ## t/t5411/common-test-cases.sh ##\n    -@@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n    - \tEOF\n    - \ttest_cmp expect actual\n    - '\n    -+\n    -+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n    -+# Refs of workbench: master(A)  tags/v123\n    -+test_expect_success \"cleanup\" '\n    -+\t(\n    -+\t\tcd \"$upstream\" &&\n    -+\t\tgit update-ref -d refs/review/master/topic &&\n    -+\t\tgit update-ref -d refs/tags/v123 &&\n    -+\t\tgit update-ref -d refs/heads/a/b/c\n    -+\t)\n    -+'\n    -+\n    + ## t/t5411/test-0011-no-hook-error.sh (new) ##\n    +@@\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       next(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: no hook, fail to push special ref\" '\n    ++test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL)\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:next \\\n     +\t\tHEAD:refs/for/master/topic \\\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\n     +# Refs of upstream : master(A)             next(A)\n     +# Refs of workbench: master(A)  tags/v123\n    -+test_expect_success \"cleanup\" '\n    ++test_expect_success \"cleanup ($PROTOCOL)\" '\n     +\tgit -C \"$upstream\" update-ref -d refs/heads/next\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: no hook, fail all for atomic push\" '\n    ++test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCOL)\" '\n     +\ttest_must_fail git -C workbench push --atomic origin \\\n     +\t\t$B:master \\\n     +\t\tHEAD:next \\\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\t<COMMIT-A> refs/heads/master\n     +\tEOF\n     +\ttest_cmp expect actual\n    ++'\n    +\n    + ## t/t5411/test-0012-no-hook-error--porcelain.sh (new) ##\n    +@@\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       next(A)  refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: no hook, fail to push special ref (--porcelain, $PROTOCOL)\" '\n    ++\ttest_must_fail git -C workbench push --porcelain origin \\\n    ++\t\tHEAD:next \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: error: cannot find hook \"proc-receive\"\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n    ++\tTo <URL/of/upstream.git>\n    ++\t*    HEAD:refs/heads/next    [new branch]\n    ++\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n    ++\tDone\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\t<COMMIT-A> refs/heads/next\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++# Refs of upstream : master(A)             next(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++test_expect_success \"cleanup ($PROTOCOL)\" '\n    ++\tgit -C \"$upstream\" update-ref -d refs/heads/next\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (unknown version)\" '\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: no hook, all failed for atomic push (--porcelain, $PROTOCOL)\" '\n    ++\ttest_must_fail git -C workbench push --porcelain --atomic origin \\\n    ++\t\t$B:master \\\n    ++\t\tHEAD:next \\\n    ++\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: error: cannot find hook \"proc-receive\"\n    ++\tTo <URL/of/upstream.git>\n    ++\t!    <COMMIT-B>:refs/heads/master    [remote rejected] (fail to run proc-receive hook)\n    ++\t!    HEAD:refs/heads/next    [remote rejected] (fail to run proc-receive hook)\n    ++\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n    ++\tDone\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    +\n    + ## t/t5411/test-0013-bad-protocol.sh (new) ##\n    +@@\n    ++test_expect_success \"setup proc-receive hook (unknown version, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v --version 2\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: bad protocol (unknown version)\" '\n    ++test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL)\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\n     +\t# Check status report for git-push\n     +\tsed -n \\\n    -+\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n    -+\t\t-e \"/^ / { s/   */ /g; p; }\" \\\n    ++\t\t-e \"/^To / { p; n; p; }\" \\\n     +\t\t<actual >actual-report &&\n     +\tcat >expect <<-EOF &&\n     +\tTo <URL/of/upstream.git>\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\t# Check error message from \"receive-pack\", but ignore unstable fatal error\n     +\t# message (\"remote: fatal: the remote end hung up unexpectedly\") which\n     +\t# is different from the remote HTTP server with different locale settings.\n    -+\tsed -n -e \"/^remote: error:/ { s/   */ /g; p; }\" \\\n    -+\t\t<actual >actual-error &&\n    ++\tgrep \"^remote: error:\" <actual >actual-error &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: error: proc-receive version \"2\" is not supported\n     +\tEOF\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (no report)\" '\n    ++test_expect_success \"setup proc-receive hook (hook --die-version, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v --die-version\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: bad protocol (hook --die-version, $PROTOCOL)\" '\n    ++\ttest_must_fail git -C workbench push origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: fatal: bad protocol version: 1\n    ++\tremote: error: proc-receive version \"0\" is not supported\n    ++\tTo <URL/of/upstream.git>\n    ++\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++test_expect_success \"setup proc-receive hook (hook --die-readline, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v --die-readline\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: bad protocol (hook --die-readline, $PROTOCOL)\" '\n    ++\ttest_must_fail git -C workbench push origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: fatal: protocol error: expected \"old new ref\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n    ++\tTo <URL/of/upstream.git>\n    ++\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++test_expect_success \"setup proc-receive hook (no report, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       next(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: bad protocol (no report)\" '\n    ++test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL)\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:refs/heads/next \\\n     +\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\n     +# Refs of upstream : master(A)             next(A)\n     +# Refs of workbench: master(A)  tags/v123\n    -+test_expect_success \"cleanup\" '\n    ++test_expect_success \"cleanup ($PROTOCOL)\" '\n     +\tgit -C \"$upstream\" update-ref -d refs/heads/next\n     +\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (no ref)\" '\n    ++test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic\n    -+test_expect_success \"proc-receive: bad protocol (no ref)\" '\n    ++test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL)\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic\\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (unknown status)\" '\n    ++test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic\n    -+test_expect_success \"proc-receive: bad protocol (unknown status)\" '\n    ++test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL)\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\t\tHEAD:refs/for/master/topic \\\n     +\t\t\t>out 2>&1 &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tEOF\n     +\ttest_cmp expect actual\n     +'\n    -+\n    -+test_expect_success \"setup proc-receive hook (ng)\" '\n    +\n    + ## t/t5411/test-0014-bad-protocol--porcelain.sh (new) ##\n    +@@\n    ++test_expect_success \"setup proc-receive hook (unknown version, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    -+\ttest-tool proc-receive -v \\\n    -+\t\t-r \"ng refs/for/master/topic\"\n    ++\ttest-tool proc-receive -v --version 2\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n    -+# git push         :                       refs/for/master/topic\n    -+test_expect_success \"proc-receive: fail to update (ng)\" '\n    -+\ttest_must_fail git -C workbench push origin \\\n    ++# git push         :                       refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: bad protocol (unknown version, --porcelain, $PROTOCOL)\" '\n    ++\ttest_must_fail git -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\n    ++\t# Check status report for git-push\n    ++\tsed -n \\\n    ++\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n    ++\t\t<actual >actual-report &&\n     +\tcat >expect <<-EOF &&\n    -+\tremote: # pre-receive hook\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: # proc-receive hook\n    -+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> ng refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    -+\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n    ++\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n    ++\tDone\n     +\tEOF\n    -+\ttest_cmp expect actual &&\n    ++\ttest_cmp expect actual-report &&\n    ++\n    ++\t# Check error message from \"receive-pack\", but ignore unstable fatal error\n    ++\t# message (\"remote: fatal: the remote end hung up unexpectedly\") which\n    ++\t# is different from the remote HTTP server with different locale settings.\n    ++\tgrep \"^remote: error:\" <actual >actual-error &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: error: proc-receive version \"2\" is not supported\n    ++\tEOF\n    ++\ttest_cmp expect actual-error &&\n    ++\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (ng message)\" '\n    ++test_expect_success \"setup proc-receive hook (no report, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n    -+\ttest-tool proc-receive -v \\\n    -+\t\t-r \"ng refs/for/master/topic error msg\"\n    ++\ttest-tool proc-receive -v\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n    -+# git push         :                       refs/for/master/topic\n    -+test_expect_success \"proc-receive: fail to update (ng message)\" '\n    -+\ttest_must_fail git -C workbench push origin \\\n    -+\t\tHEAD:refs/for/master/topic \\\n    -+\t\t>out 2>&1 &&\n    ++# git push         :                       next(A)  refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: bad protocol (no report, --porcelain, $PROTOCOL)\" '\n    ++\ttest_must_fail git -C workbench push --porcelain origin \\\n    ++\t\tHEAD:refs/heads/next \\\n    ++\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> ng refs/for/master/topic error msg\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n     +\tTo <URL/of/upstream.git>\n    -+\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n    ++\t*    HEAD:refs/heads/next    [new branch]\n    ++\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n    ++\tDone\n     +\tEOF\n     +\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n    ++\t<COMMIT-A> refs/heads/next\n     +\tEOF\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (unexpected ref)\" '\n    ++# Refs of upstream : master(A)             next(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++test_expect_success \"cleanup ($PROTOCOL)\" '\n    ++\tgit -C \"$upstream\" update-ref -d refs/heads/next\n    ++\n    ++'\n    ++\n    ++test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"ok refs/heads/master\"\n    ++\t\t-r \"ok\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n    -+# git push         : (B)                   refs/for/master/topic\n    -+test_expect_success \"proc-receive: report on unexpected ref\" '\n    -+\ttest_must_fail git -C workbench push origin \\\n    -+\t\t$B:refs/heads/master \\\n    -+\t\tHEAD:refs/for/master/topic \\\n    ++# git push         :                       refs/for/master/topic\n    ++test_expect_success \"proc-receive: bad protocol (no ref, --porcelain, $PROTOCOL)\" '\n    ++\ttest_must_fail git -C workbench push --porcelain origin \\\n    ++\t\tHEAD:refs/for/master/topic\\\n     +\t\t>out 2>&1 &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: # pre-receive hook\n    -+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> ok refs/heads/master\n    -+\tremote: error: proc-receive reported status on unexpected ref: refs/heads/master\n    -+\tremote: # post-receive hook\n    -+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n    ++\tremote: proc-receive> ok\n    ++\tremote: error: proc-receive expected \"<status> <ref> ...\", got \"ok\"\n     +\tTo <URL/of/upstream.git>\n    -+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n    -+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n    ++\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n    ++\tDone\n     +\tEOF\n     +\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    -+\t<COMMIT-B> refs/heads/master\n    ++\t<COMMIT-A> refs/heads/master\n     +\tEOF\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+# Refs of upstream : master(B)\n    -+# Refs of workbench: master(A)  tags/v123\n    -+test_expect_success \"cleanup\" '\n    -+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n    -+'\n    -+\n    -+test_expect_success \"setup proc-receive hook (ok)\" '\n    ++test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"ok refs/for/master/topic\"\n    ++\t\t-r \"xx refs/for/master/topic\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic\n    -+test_expect_success \"proc-receive: ok\" '\n    -+\tgit -C workbench push origin \\\n    -+\t\tHEAD:refs/for/master/topic \\\n    -+\t\t>out 2>&1 &&\n    ++test_expect_success \"proc-receive: bad protocol (unknown status, --porcelain, $PROTOCOL)\" '\n    ++\ttest_must_fail git -C workbench push --porcelain origin \\\n    ++\t\t\tHEAD:refs/for/master/topic \\\n    ++\t\t\t>out 2>&1 &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: # pre-receive hook\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> ok refs/for/master/topic\n    -+\tremote: # post-receive hook\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> xx refs/for/master/topic\n    ++\tremote: error: proc-receive reported bad status \"xx\" on ref \"refs/for/master/topic\"\n     +\tTo <URL/of/upstream.git>\n    -+\t * [new reference] HEAD -> refs/for/master/topic\n    ++\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n    ++\tDone\n     +\tEOF\n     +\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\t<COMMIT-A> refs/heads/master\n     +\tEOF\n     +\ttest_cmp expect actual\n    ++'\n    +\n    + ## t/t5411/test-0020-report-ng.sh (new) ##\n    +@@\n    ++test_expect_success \"setup proc-receive hook (ng, no message, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"ng refs/for/master/topic\"\n    ++\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n    -+# git push         :                       refs/for/a/b/c/my/topic\n    -+test_expect_success \"proc-receive: failed to report status\" '\n    ++# git push         :                       refs/for/master/topic\n    ++test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL)\" '\n     +\ttest_must_fail git -C workbench push origin \\\n    -+\t\tHEAD:refs/for/a/b/c/my/topic \\\n    ++\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: # pre-receive hook\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n    -+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n    -+\tremote: proc-receive> ok refs/for/master/topic\n    -+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> ng refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    -+\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (proc-receive failed to report status)\n    ++\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n     +\tEOF\n     +\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+# Refs of upstream : master(A)\n    -+# Refs of workbench: master(A)  tags/v123\n    -+# git push -o ...  :                       refs/for/master/topic\n    -+test_expect_success \"proc-receive: not support push options\" '\n    -+\ttest_must_fail git -C workbench push \\\n    ++test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"ng refs/for/master/topic error msg\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/master/topic\n    ++test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL)\" '\n    ++\ttest_must_fail git -C workbench push origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> ng refs/for/master/topic error msg\n    ++\tTo <URL/of/upstream.git>\n    ++\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    +\n    + ## t/t5411/test-0021-report-ng--porcelain.sh (new) ##\n    +@@\n    ++test_expect_success \"setup proc-receive hook (ng, no message, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"ng refs/for/master/topic\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/master/topic\n    ++test_expect_success \"proc-receive: fail to update (ng, no message, --porcelain, $PROTOCOL)\" '\n    ++\ttest_must_fail git -C workbench push --porcelain origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> ng refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t!    HEAD:refs/for/master/topic    [remote rejected] (failed)\n    ++\tDone\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"ng refs/for/master/topic error msg\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/master/topic\n    ++test_expect_success \"proc-receive: fail to update (ng, with message, --porcelain, $PROTOCOL)\" '\n    ++\ttest_must_fail git -C workbench push --porcelain origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> ng refs/for/master/topic error msg\n    ++\tTo <URL/of/upstream.git>\n    ++\t!    HEAD:refs/for/master/topic    [remote rejected] (error msg)\n    ++\tDone\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    +\n    + ## t/t5411/test-0022-report-unexpect-ref.sh (new) ##\n    +@@\n    ++test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"ok refs/heads/master\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         : (B)                   refs/for/master/topic\n    ++test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL)\" '\n    ++\ttest_must_fail git -C workbench push origin \\\n    ++\t\t$B:refs/heads/master \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> ok refs/heads/master\n    ++\tremote: error: proc-receive reported status on unexpected ref: refs/heads/master\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n    ++\tTo <URL/of/upstream.git>\n    ++\t <OID-A>..<OID-B> <COMMIT-B> -> master\n    ++\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-B> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++# Refs of upstream : master(B)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++test_expect_success \"cleanup ($PROTOCOL)\" '\n    ++\tgit -C \"$upstream\" update-ref refs/heads/master $A\n    ++'\n    +\n    + ## t/t5411/test-0023-report-unexpect-ref--porcelain.sh (new) ##\n    +@@\n    ++test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"ok refs/heads/master\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         : (B)                   refs/for/master/topic\n    ++test_expect_success \"proc-receive: report unexpected ref (--porcelain, $PROTOCOL)\" '\n    ++\ttest_must_fail git -C workbench push --porcelain origin \\\n    ++\t\t$B:refs/heads/master \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> ok refs/heads/master\n    ++\tremote: error: proc-receive reported status on unexpected ref: refs/heads/master\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n    ++\tTo <URL/of/upstream.git>\n    ++\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n    ++\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n    ++\tDone\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-B> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++# Refs of upstream : master(B)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++test_expect_success \"cleanup ($PROTOCOL)\" '\n    ++\tgit -C \"$upstream\" update-ref refs/heads/master $A\n    ++'\n    +\n    + ## t/t5411/test-0024-report-unknown-ref.sh (new) ##\n    +@@\n    ++test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"ok refs/for/master/topic\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/a/b/c/my/topic\n    ++test_expect_success \"proc-receive: report unknown reference ($PROTOCOL)\" '\n    ++\ttest_must_fail git -C workbench push origin \\\n    ++\t\tHEAD:refs/for/a/b/c/my/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (proc-receive failed to report status)\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    +\n    + ## t/t5411/test-0025-report-unknown-ref--porcelain.sh (new) ##\n    +@@\n    ++test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"ok refs/for/master/topic\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/a/b/c/my/topic\n    ++test_expect_success \"proc-receive: report unknown reference (--porcelain, $PROTOCOL)\" '\n    ++\ttest_must_fail git -C workbench push --porcelain origin \\\n    ++\t\tHEAD:refs/for/a/b/c/my/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t!    HEAD:refs/for/a/b/c/my/topic    [remote rejected] (proc-receive failed to report status)\n    ++\tDone\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    +\n    + ## t/t5411/test-0026-push-options.sh (new) ##\n    +@@\n    ++test_expect_success \"setup proc-receive hook and disable push-options ($PROTOCOL)\" '\n    ++\tgit -C \"$upstream\" config receive.advertisePushOptions false &&\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"ok refs/for/master/topic\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push -o ...  :                       refs/for/master/topic\n    ++test_expect_success \"proc-receive: not support push options ($PROTOCOL)\" '\n    ++\ttest_must_fail git -C workbench push \\\n     +\t\t-o issue=123 \\\n     +\t\t-o reviewer=user1 \\\n     +\t\torigin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n    -+\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n    -+\t\tactual &&\n    ++\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n    ++\t\tactual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++test_expect_success \"enable push options ($PROTOCOL)\" '\n    ++\tgit -C \"$upstream\" config receive.advertisePushOptions true\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push -o ...  :                       next(A)  refs/for/master/topic\n    ++test_expect_success \"proc-receive: push with options ($PROTOCOL)\" '\n    ++\tgit -C workbench push \\\n    ++\t\t--atomic \\\n    ++\t\t-o issue=123 \\\n    ++\t\t-o reviewer=user1 \\\n    ++\t\torigin \\\n    ++\t\tHEAD:refs/heads/next \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive: atomic push_options\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive< issue=123\n    ++\tremote: proc-receive< reviewer=user1\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t * [new branch] HEAD -> next\n    ++\t * [new reference] HEAD -> refs/for/master/topic\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\t<COMMIT-A> refs/heads/next\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++# Refs of upstream : master(A)             next(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++test_expect_success \"cleanup ($PROTOCOL)\" '\n    ++\tgit -C \"$upstream\" update-ref -d refs/heads/next\n    ++'\n    +\n    + ## t/t5411/test-0027-push-options--porcelain.sh (new) ##\n    +@@\n    ++test_expect_success \"setup proc-receive hook and disable push-options ($PROTOCOL)\" '\n    ++\tgit -C \"$upstream\" config receive.advertisePushOptions false &&\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"ok refs/for/master/topic\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push -o ...  :                       refs/for/master/topic\n    ++test_expect_success \"proc-receive: not support push options (--porcelain, $PROTOCOL)\" '\n    ++\ttest_must_fail git -C workbench push \\\n    ++\t\t--porcelain \\\n    ++\t\t-o issue=123 \\\n    ++\t\t-o reviewer=user1 \\\n    ++\t\torigin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n    ++\t\tactual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++test_expect_success \"enable push options ($PROTOCOL)\" '\n    ++\tgit -C \"$upstream\" config receive.advertisePushOptions true\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push -o ...  :                       next(A)  refs/for/master/topic\n    ++test_expect_success \"proc-receive: push with options (--porcelain, $PROTOCOL)\" '\n    ++\tgit -C workbench push \\\n    ++\t\t--porcelain \\\n    ++\t\t--atomic \\\n    ++\t\t-o issue=123 \\\n    ++\t\t-o reviewer=user1 \\\n    ++\t\torigin \\\n    ++\t\tHEAD:refs/heads/next \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive: atomic push_options\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive< issue=123\n    ++\tremote: proc-receive< reviewer=user1\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t*    HEAD:refs/heads/next    [new branch]\n    ++\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\tDone\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\t<COMMIT-A> refs/heads/next\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++# Refs of upstream : master(A)             next(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++test_expect_success \"cleanup ($PROTOCOL)\" '\n    ++\tgit -C \"$upstream\" update-ref -d refs/heads/next\n    ++'\n    +\n    + ## t/t5411/test-0030-report-ok.sh (new) ##\n    +@@\n    ++test_expect_success \"setup proc-receive hook (ok, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"ok refs/for/master/topic\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/master/topic\n    ++test_expect_success \"proc-receive: ok ($PROTOCOL)\" '\n    ++\tgit -C workbench push origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t * [new reference] HEAD -> refs/for/master/topic\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    +\n    + ## t/t5411/test-0031-report-ok--porcelain.sh (new) ##\n    +@@\n    ++test_expect_success \"setup proc-receive hook (ok, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"ok refs/for/master/topic\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/master/topic\n    ++test_expect_success \"proc-receive: ok (--porcelain, $PROTOCOL)\" '\n    ++\tgit -C workbench push --porcelain origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\tDone\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    +\n    + ## t/t5411/test-0032-report-alt.sh (new) ##\n    +@@\n    ++test_expect_success \"setup proc-receive hook (alt <ref>, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"alt refs/for/master/topic\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report alt (alt <ref>, $PROTOCOL)\" '\n    ++\ttest_must_fail git -C workbench push origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> alt refs/for/master/topic\n    ++\tremote: error: proc-receive expected \"alt <ref> <alt-ref> ...\", got \"alt refs/for/master/topic\"\n    ++\tTo <URL/of/upstream.git>\n    ++\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++test_expect_success \"setup proc-receive hook (alt <ref> <alt-ref>, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"alt refs/for/master/topic refs/pull/123/head\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref>, $PROTOCOL)\" '\n    ++\tgit -C workbench push origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t * [new reference] HEAD -> refs/for/master/topic\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++test_expect_success \"setup proc-receive hook (alt <ref> <alt-ref> forced-update, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"alt refs/for/master/topic refs/pull/123/head forced-update\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> forced-update, $PROTOCOL)\" '\n    ++\tgit -C workbench push origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head forced-update\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t * [new reference] HEAD -> refs/for/master/topic\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++test_expect_success \"setup proc-receive hook (alt <ref> <alt-ref> old-oid=X, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"alt refs/for/master/topic refs/pull/123/head old-oid=$B\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> old-oid=X, $PROTOCOL)\" '\n    ++\tgit -C workbench push origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head old-oid=<COMMIT-B>\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t * [new reference] HEAD -> refs/for/master/topic\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++test_expect_success \"setup proc-receive hook (alt <ref> old-oid=X, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"alt refs/for/master/topic old-oid=$B\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X, $PROTOCOL)\" '\n    ++\tgit -C workbench push origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-B>\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t * [new reference] HEAD -> refs/for/master/topic\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++test_expect_success \"setup proc-receive hook (alt <ref> old-oid=X new-oid=Y, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"alt refs/for/master/topic old-oid=$A new-oid=$B\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X new-oid=Y, $PROTOCOL)\" '\n    ++\tgit -C workbench push origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t * [new reference] HEAD -> refs/for/master/topic\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++test_expect_success \"setup proc-receive hook (with multiple alt reports, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"ok refs/for/a/b/c/topic\" \\\n    ++\t\t-r \"alt refs/for/next/topic refs/pull/123/head\" \\\n    ++\t\t-r \"alt refs/for/master/topic refs/pull/124/head old-oid=$B forced-update new-oid=$A\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: with multiple alt reports ($PROTOCOL)\" '\n    ++\tgit -C workbench push origin \\\n    ++\t\tHEAD:refs/for/next/topic \\\n    ++\t\tHEAD:refs/for/a/b/c/topic \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> ok refs/for/a/b/c/topic\n    ++\tremote: proc-receive> alt refs/for/next/topic refs/pull/123/head\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/pull/124/head old-oid=<COMMIT-B> forced-update new-oid=<COMMIT-A>\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t * [new reference] HEAD -> refs/for/next/topic\n    ++\t * [new reference] HEAD -> refs/for/a/b/c/topic\n    ++\t * [new reference] HEAD -> refs/for/master/topic\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    +\n    + ## t/t5411/test-0033-report-alt--porcelain.sh (new) ##\n    +@@\n    ++test_expect_success \"setup proc-receive hook (alt <ref>, --porcelain, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"alt refs/for/master/topic\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report alt (alt <ref>, --porcelain, $PROTOCOL)\" '\n    ++\ttest_must_fail git -C workbench push --porcelain origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> alt refs/for/master/topic\n    ++\tremote: error: proc-receive expected \"alt <ref> <alt-ref> ...\", got \"alt refs/for/master/topic\"\n    ++\tTo <URL/of/upstream.git>\n    ++\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n    ++\tDone\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++test_expect_success \"setup proc-receive hook (alt <ref> <alt-ref>, --porcelain, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"alt refs/for/master/topic refs/pull/123/head\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref>, --porcelain, $PROTOCOL)\" '\n    ++\tgit -C workbench push --porcelain origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\tDone\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++test_expect_success \"setup proc-receive hook (alt <ref> <alt-ref> forced-update, --porcelain, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"alt refs/for/master/topic refs/pull/123/head forced-update\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> forced-update, --porcelain, $PROTOCOL)\" '\n    ++\tgit -C workbench push --porcelain origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head forced-update\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\tDone\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++test_expect_success \"setup proc-receive hook (alt <ref> <alt-ref> old-oid=X, --porcelain, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"alt refs/for/master/topic refs/pull/123/head old-oid=$B\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> old-oid=X, --porcelain, $PROTOCOL)\" '\n    ++\tgit -C workbench push --porcelain origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head old-oid=<COMMIT-B>\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\tDone\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++test_expect_success \"setup proc-receive hook (alt <ref> old-oid=X, --porcelain, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"alt refs/for/master/topic old-oid=$B\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X, --porcelain, $PROTOCOL)\" '\n    ++\tgit -C workbench push --porcelain origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-B>\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\tDone\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++test_expect_success \"setup proc-receive hook (alt <ref> old-oid=X new-oid=Y, --porcelain, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"alt refs/for/master/topic old-oid=$A new-oid=$B\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X new-oid=Y, --porcelain, $PROTOCOL)\" '\n    ++\tgit -C workbench push --porcelain origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\tDone\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++test_expect_success \"setup proc-receive hook (with multiple alt reports, --porcelain, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"ok refs/for/a/b/c/topic\" \\\n    ++\t\t-r \"alt refs/for/next/topic refs/pull/123/head\" \\\n    ++\t\t-r \"alt refs/for/master/topic refs/pull/124/head old-oid=$B forced-update new-oid=$A\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: with multiple alt reports (--porcelain, $PROTOCOL)\" '\n    ++\tgit -C workbench push --porcelain origin \\\n    ++\t\tHEAD:refs/for/next/topic \\\n    ++\t\tHEAD:refs/for/a/b/c/topic \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> ok refs/for/a/b/c/topic\n    ++\tremote: proc-receive> alt refs/for/next/topic refs/pull/123/head\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/pull/124/head old-oid=<COMMIT-B> forced-update new-oid=<COMMIT-A>\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t*    HEAD:refs/for/next/topic    [new reference]\n    ++\t*    HEAD:refs/for/a/b/c/topic    [new reference]\n    ++\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\tDone\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    +\n    + ## t/t5411/test-0034-report-ft.sh (new) ##\n    +@@\n    ++test_expect_success \"setup proc-receive hook (ft, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"ft refs/for/master/topic\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/master/topic(B)\n    ++test_expect_success \"proc-receive: fall throught, let receive-pack to execute ($PROTOCOL)\" '\n    ++\tgit -C workbench push origin \\\n    ++\t\t$B:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n    ++\tremote: proc-receive> ft refs/for/master/topic\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t * [new reference] <COMMIT-B> -> refs/for/master/topic\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-B> refs/for/master/topic\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++# Refs of upstream : master(A)             refs/for/master/topic(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++test_expect_success \"cleanup ($PROTOCOL)\" '\n    ++\tgit -C \"$upstream\" update-ref -d refs/for/master/topic\n    ++'\n    +\n    + ## t/t5411/test-0035-report-ft--porcelain.sh (new) ##\n    +@@\n    ++test_expect_success \"setup proc-receive hook (ft, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"ft refs/for/master/topic\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/master/topic(B)\n    ++test_expect_success \"proc-receive: fall throught, let receive-pack to execute (--porcelain, $PROTOCOL)\" '\n    ++\tgit -C workbench push --porcelain origin \\\n    ++\t\t$B:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n    ++\tremote: proc-receive> ft refs/for/master/topic\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t*    <COMMIT-B>:refs/for/master/topic    [new reference]\n    ++\tDone\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-B> refs/for/master/topic\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++# Refs of upstream : master(A)             refs/for/master/topic(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++test_expect_success \"cleanup ($PROTOCOL)\" '\n    ++\tgit -C \"$upstream\" update-ref -d refs/for/master/topic\n    ++'\n    +\n    + ## t/t5411/test-0036-report-multi-alt-for-one-ref.sh (new) ##\n    +@@\n    ++test_expect_success \"setup proc-receive hook (multiple alt, no alt-ref for the 1st resp, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"alt refs/for/master/topic old-oid=$A new-oid=$B\" \\\n    ++\t\t-r \"alt refs/for/master/topic refs/changes/24/124/1 old-oid=$ZERO_OID new-oid=$A\" \\\n    ++\t\t-r \"alt refs/for/master/topic refs/changes/25/125/1 old-oid=$A new-oid=$B\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 1st resp ($PROTOCOL)\" '\n    ++\tgit -C workbench push origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 old-oid=<ZERO-OID> new-oid=<COMMIT-A>\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/changes/25/125/1 old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t * [new reference] HEAD -> refs/for/master/topic\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"enable push options\" '\n    -+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n    ++test_expect_success \"setup proc-receive hook (multiple alt, no alt-ref for the 2nd resp, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"alt refs/for/master/topic refs/changes/24/124/1 old-oid=$ZERO_OID new-oid=$A\" \\\n    ++\t\t-r \"alt refs/for/master/topic old-oid=$A new-oid=$B\" \\\n    ++\t\t-r \"alt refs/for/master/topic refs/changes/25/125/1 old-oid=$B new-oid=$A forced-update\"\n    ++\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n    -+# git push -o ...  :                       next(A)  refs/for/master/topic\n    -+test_expect_success \"proc-receive: push with options\" '\n    -+\tgit -C workbench push \\\n    -+\t\t--atomic \\\n    -+\t\t-o issue=123 \\\n    -+\t\t-o reviewer=user1 \\\n    -+\t\torigin \\\n    -+\t\tHEAD:refs/heads/next \\\n    ++# git push         :                       refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 2nd resp ($PROTOCOL)\" '\n    ++\tgit -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: # pre-receive hook\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n    -+\tremote: proc-receive: atomic push_options\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive< issue=123\n    -+\tremote: proc-receive< reviewer=user1\n    -+\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 old-oid=<ZERO-OID> new-oid=<COMMIT-A>\n    ++\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/changes/25/125/1 old-oid=<COMMIT-B> new-oid=<COMMIT-A> forced-update\n     +\tremote: # post-receive hook\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    -+\t * [new branch] HEAD -> next\n     +\t * [new reference] HEAD -> refs/for/master/topic\n     +\tEOF\n     +\ttest_cmp expect actual &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n    -+\t<COMMIT-A> refs/heads/next\n     +\tEOF\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+# Refs of upstream : master(A)             next(A)\n    ++test_expect_success \"setup proc-receive hook (ok and alt for the same ref, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"alt refs/for/master/topic refs/changes/24/124/1 new-oid=$B old-oid=$A\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n    -+test_expect_success \"cleanup\" '\n    -+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n    ++# git push         :                       refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report ok and alt for the same ref ($PROTOCOL)\" '\n    ++\tgit -C workbench push origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 new-oid=<COMMIT-B> old-oid=<COMMIT-A>\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t * [new reference] HEAD -> refs/for/master/topic\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (ft)\" '\n    ++test_expect_success \"setup proc-receive hook (multiple responses, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"ft refs/for/master/topic\"\n    ++\t\t-r \"alt refs/for/master/topic refs/changes/23/123/1\" \\\n    ++\t\t-r \"alt refs/for/master/topic refs/changes/24/124/2 old-oid=$A new-oid=$B\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n    -+# git push         :                       refs/for/master/topic(B)\n    -+test_expect_success \"proc-receive: fall throught, let receive-pack to execute\" '\n    ++# git push         :                       refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report multiple response ($PROTOCOL)\" '\n     +\tgit -C workbench push origin \\\n    -+\t\t$B:refs/for/master/topic \\\n    ++\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: # pre-receive hook\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n    -+\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n    -+\tremote: proc-receive> ft refs/for/master/topic\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/changes/23/123/1\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/2 old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n     +\tremote: # post-receive hook\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    -+\t * [new reference] <COMMIT-B> -> refs/for/master/topic\n    ++\t * [new reference] HEAD -> refs/for/master/topic\n     +\tEOF\n     +\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    -+\t<COMMIT-B> refs/for/master/topic\n     +\t<COMMIT-A> refs/heads/master\n     +\tEOF\n     +\ttest_cmp expect actual\n    ++'\n    +\n    + ## t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh (new) ##\n    +@@\n    ++test_expect_success \"setup proc-receive hook (multiple alt, no alt-ref for the 1st resp, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"alt refs/for/master/topic old-oid=$A new-oid=$B\" \\\n    ++\t\t-r \"alt refs/for/master/topic refs/changes/24/124/1 old-oid=$ZERO_OID new-oid=$A\" \\\n    ++\t\t-r \"alt refs/for/master/topic refs/changes/25/125/1 old-oid=$A new-oid=$B\"\n    ++\tEOF\n     +'\n     +\n    -+# Refs of upstream : master(A)             refs/for/master/topic(A)\n    ++# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n    -+test_expect_success \"cleanup\" '\n    -+\tgit -C \"$upstream\" update-ref -d refs/for/master/topic\n    ++# git push         :                       refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 1st resp (--porcelain, $PROTOCOL)\" '\n    ++\tgit -C workbench push --porcelain origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 old-oid=<ZERO-OID> new-oid=<COMMIT-A>\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/changes/25/125/1 old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\tDone\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (with extended status)\" '\n    ++test_expect_success \"setup proc-receive hook (multiple alt, no alt-ref for the 2nd resp, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"ok refs/for/a/b/c/topic\" \\\n    -+\t\t-r \"alt refs/for/next/topic refs/pull/123/head\" \\\n    -+\t\t-r \"alt refs/for/master/topic refs/pull/124/head old-oid=$B forced-update new-oid=$A\"\n    ++\t\t-r \"alt refs/for/master/topic refs/changes/24/124/1 old-oid=$ZERO_OID new-oid=$A\" \\\n    ++\t\t-r \"alt refs/for/master/topic old-oid=$A new-oid=$B\" \\\n    ++\t\t-r \"alt refs/for/master/topic refs/changes/25/125/1 old-oid=$B new-oid=$A forced-update\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n    -+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report with extended status\" '\n    -+\tgit -C workbench push origin \\\n    -+\t\tHEAD:refs/for/next/topic \\\n    -+\t\tHEAD:refs/for/a/b/c/topic \\\n    ++# git push         :                       refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 2nd resp (--porcelain, $PROTOCOL)\" '\n    ++\tgit -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: # pre-receive hook\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n    -+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    -+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> ok refs/for/a/b/c/topic\n    -+\tremote: proc-receive> alt refs/for/next/topic refs/pull/123/head\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/pull/124/head old-oid=<COMMIT-B> forced-update new-oid=<COMMIT-A>\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 old-oid=<ZERO-OID> new-oid=<COMMIT-A>\n    ++\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/changes/25/125/1 old-oid=<COMMIT-B> new-oid=<COMMIT-A> forced-update\n     +\tremote: # post-receive hook\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    -+\t * [new reference] HEAD -> refs/for/next/topic\n    -+\t * [new reference] HEAD -> refs/for/a/b/c/topic\n    -+\t * [new reference] HEAD -> refs/for/master/topic\n    ++\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\tDone\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++test_expect_success \"setup proc-receive hook (ok and alt for the same ref, $PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"alt refs/for/master/topic refs/changes/24/124/1 new-oid=$B old-oid=$A\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         :                       refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report ok and alt for the same ref (--porcelain, $PROTOCOL)\" '\n    ++\tgit -C workbench push --porcelain origin \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 new-oid=<COMMIT-B> old-oid=<COMMIT-A>\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\tDone\n     +\tEOF\n     +\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (multiple responses)\" '\n    ++test_expect_success \"setup proc-receive hook (multiple responses, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report multiple response\" '\n    -+\tgit -C workbench push origin \\\n    ++test_expect_success \"proc-receive: report multiple response (--porcelain, $PROTOCOL)\" '\n    ++\tgit -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    -+\t * [new reference] HEAD -> refs/for/master/topic\n    ++\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\tDone\n     +\tEOF\n     +\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tEOF\n     +\ttest_cmp expect actual\n     +'\n    -+\n    -+test_expect_success \"setup proc-receive hook\" '\n    +\n    + ## t/t5411/test-0038-report-mixed-refs.sh (new) ##\n    +@@\n    ++test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report update of mixed refs\" '\n    ++test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n     +\tgit -C workbench push origin \\\n     +\t\t$B:refs/heads/master \\\n     +\t\tHEAD:refs/heads/bar \\\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"normal git-push command\" '\n     +\tEOF\n     +\ttest_cmp expect actual\n     +'\n    ++\n    ++# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++test_expect_success \"cleanup ($PROTOCOL)\" '\n    ++\t(\n    ++\t\tcd \"$upstream\" &&\n    ++\t\tgit update-ref refs/heads/master $A &&\n    ++\t\tgit update-ref -d refs/heads/foo &&\n    ++\t\tgit update-ref -d refs/heads/bar &&\n    ++\t\tgit update-ref -d refs/heads/baz\n    ++\t)\n    ++\n    ++'\n    +\n    + ## t/t5411/test-0039-report-mixed-refs--porcelain.sh (new) ##\n    +@@\n    ++test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"ok refs/for/next/topic\" \\\n    ++\t\t-r \"alt refs/for/master/topic refs/for/master/topic old-oid=$A new-oid=$B\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n    ++test_expect_success \"proc-receive: report update of mixed refs (--porcelain, $PROTOCOL)\" '\n    ++\tgit -C workbench push --porcelain origin \\\n    ++\t\t$B:refs/heads/master \\\n    ++\t\tHEAD:refs/heads/bar \\\n    ++\t\tHEAD:refs/heads/baz \\\n    ++\t\tHEAD:refs/for/next/topic \\\n    ++\t\tHEAD:refs/heads/foo \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive> ok refs/for/next/topic\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tTo <URL/of/upstream.git>\n    ++\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n    ++\t*    HEAD:refs/heads/bar    [new branch]\n    ++\t*    HEAD:refs/heads/baz    [new branch]\n    ++\t*    HEAD:refs/for/next/topic    [new reference]\n    ++\t*    HEAD:refs/heads/foo    [new branch]\n    ++\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\tDone\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/heads/bar\n    ++\t<COMMIT-A> refs/heads/baz\n    ++\t<COMMIT-A> refs/heads/foo\n    ++\t<COMMIT-B> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++test_expect_success \"cleanup ($PROTOCOL)\" '\n    ++\t(\n    ++\t\tcd \"$upstream\" &&\n    ++\t\tgit update-ref refs/heads/master $A &&\n    ++\t\tgit update-ref -d refs/heads/foo &&\n    ++\t\tgit update-ref -d refs/heads/bar &&\n    ++\t\tgit update-ref -d refs/heads/baz\n    ++\t)\n    ++\n    ++'\n4:  d20702291b ! 4:  f03038c82f send-pack: extension for client-side status report\n    @@ remote.c: void apply_push_cas(struct push_cas_option *cas,\n     +void update_ref_from_remote_status(struct ref *ref)\n     +{\n     +\tchar *val;\n    -+\tchar c;\n     +\tint len;\n     +\n     +\tif (!ref->remote_status)\n     +\t\treturn;\n     +\n     +\tval = (char *)parse_feature_value(ref->remote_status, \"old-oid\", &len);\n    -+\tif (val && len) {\n    -+\t\tc = *(val+len);\n    -+\t\t*(val+len) = '\\0';\n    ++\tif (val && len)\n     +\t\tget_oid_hex(val, &ref->old_oid);\n    -+\t\t*(val+len) = c;\n    -+\t}\n    ++\n     +\tval = (char *)parse_feature_value(ref->remote_status, \"new-oid\", &len);\n    -+\tif (val && len) {\n    -+\t\tc = *(val+len);\n    -+\t\t*(val+len) = '\\0';\n    ++\tif (val && len)\n     +\t\tget_oid_hex(val, &ref->new_oid);\n    -+\t\t*(val+len) = c;\n    -+\t}\n    ++\n     +\tif (parse_feature_request(ref->remote_status, \"forced-update\"))\n     +\t\tref->forced_update = 1;\n     +}\n    @@ send-pack.c: static int receive_status(struct packet_reader *reader, struct ref\n      \t\t/* first try searching at our hint, falling back to all refs */\n      \t\tif (hint)\n     @@ send-pack.c: static int receive_status(struct packet_reader *reader, struct ref *refs)\n    + \t\t\thint->status = REF_STATUS_OK;\n    + \t\telse\n      \t\t\thint->status = REF_STATUS_REMOTE_REJECT;\n    - \t\t\tret = -1;\n    - \t\t}\n     -\t\thint->remote_status = xstrdup_or_null(msg);\n     +\t\tif (msg)\n     +\t\t\thint->remote_status = xstrdup(msg);\n     +\t\telse if (extended_status)\n    -+\t\t\thint->remote_status = xstrdup(extended_status);\n    ++\t\t\thint->remote_status = xstrdup_or_null(extended_status);\n      \t\t/* start our next search from the next ref */\n      \t\thint = hint->next;\n      \t}\n     \n    - ## t/t5411/common-test-cases.sh ##\n    -@@ t/t5411/common-test-cases.sh: test_expect_success \"proc-receive: report with extended status\" '\n    + ## t/t5411/test-0032-report-alt.sh ##\n    +@@ t/t5411/test-0032-report-alt.sh: test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref>, $PROTOCOL)\"\n    + \tremote: # post-receive hook\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    + \tTo <URL/of/upstream.git>\n    +-\t * [new reference] HEAD -> refs/for/master/topic\n    ++\t * [new reference] HEAD -> refs/pull/123/head\n    + \tEOF\n    + \ttest_cmp expect actual\n    + '\n    +@@ t/t5411/test-0032-report-alt.sh: test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> forced-update\n    + \tremote: # post-receive hook\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    + \tTo <URL/of/upstream.git>\n    +-\t * [new reference] HEAD -> refs/for/master/topic\n    ++\t * [new reference] HEAD -> refs/pull/123/head\n    + \tEOF\n    + \ttest_cmp expect actual\n    + '\n    +@@ t/t5411/test-0032-report-alt.sh: test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> old-oid=X, $P\n    + \tremote: # post-receive hook\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    + \tTo <URL/of/upstream.git>\n    +-\t * [new reference] HEAD -> refs/for/master/topic\n    ++\t <OID-B>..<OID-A> HEAD -> refs/pull/123/head\n    + \tEOF\n    + \ttest_cmp expect actual\n    + '\n    +@@ t/t5411/test-0032-report-alt.sh: test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X, $PROTOCOL)\"\n    + \tremote: # post-receive hook\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    + \tTo <URL/of/upstream.git>\n    +-\t * [new reference] HEAD -> refs/for/master/topic\n    ++\t <OID-B>..<OID-A> HEAD -> refs/for/master/topic\n    + \tEOF\n    + \ttest_cmp expect actual\n    + '\n    +@@ t/t5411/test-0032-report-alt.sh: test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X new-oid=Y, $P\n    + \tremote: # post-receive hook\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    + \tTo <URL/of/upstream.git>\n    +-\t * [new reference] HEAD -> refs/for/master/topic\n    ++\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n    + \tEOF\n    + \ttest_cmp expect actual\n    + '\n    +@@ t/t5411/test-0032-report-alt.sh: test_expect_success \"proc-receive: with multiple alt reports ($PROTOCOL)\" '\n      \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n      \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n      \tTo <URL/of/upstream.git>\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"proc-receive: report with ext\n     +\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n      \tEOF\n      \ttest_cmp expect actual &&\n    + \n    +\n    + ## t/t5411/test-0033-report-alt--porcelain.sh ##\n    +@@ t/t5411/test-0033-report-alt--porcelain.sh: test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref>, --porcelain,\n    + \tremote: # post-receive hook\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    + \tTo <URL/of/upstream.git>\n    +-\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\t*    HEAD:refs/pull/123/head    [new reference]\n    + \tDone\n    + \tEOF\n    + \ttest_cmp expect actual\n    +@@ t/t5411/test-0033-report-alt--porcelain.sh: test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> forced-update\n    + \tremote: # post-receive hook\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    + \tTo <URL/of/upstream.git>\n    +-\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\t*    HEAD:refs/pull/123/head    [new reference]\n    + \tDone\n    + \tEOF\n    + \ttest_cmp expect actual\n    +@@ t/t5411/test-0033-report-alt--porcelain.sh: test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> old-oid=X, --\n    + \tremote: # post-receive hook\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    + \tTo <URL/of/upstream.git>\n    +-\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\t     HEAD:refs/pull/123/head    <OID-B>..<OID-A>\n    + \tDone\n    + \tEOF\n    + \ttest_cmp expect actual\n    +@@ t/t5411/test-0033-report-alt--porcelain.sh: test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X, --porcelain,\n    + \tremote: # post-receive hook\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    + \tTo <URL/of/upstream.git>\n    +-\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\t     HEAD:refs/for/master/topic    <OID-B>..<OID-A>\n    + \tDone\n    + \tEOF\n    + \ttest_cmp expect actual\n    +@@ t/t5411/test-0033-report-alt--porcelain.sh: test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X new-oid=Y, --\n    + \tremote: # post-receive hook\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    + \tTo <URL/of/upstream.git>\n    +-\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n    + \tDone\n    + \tEOF\n    + \ttest_cmp expect actual\n    +@@ t/t5411/test-0033-report-alt--porcelain.sh: test_expect_success \"proc-receive: with multiple alt reports (--porcelain, $PROT\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    + \tTo <URL/of/upstream.git>\n    +-\t*    HEAD:refs/for/next/topic    [new reference]\n    ++\t*    HEAD:refs/pull/123/head    [new reference]\n    + \t*    HEAD:refs/for/a/b/c/topic    [new reference]\n    +-\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\t+    HEAD:refs/pull/124/head    <OID-B>...<OID-A> (forced update)\n    + \tDone\n    + \tEOF\n    + \ttest_cmp expect actual &&\n    +\n    + ## t/t5411/test-0036-report-multi-alt-for-one-ref.sh ##\n    +@@ t/t5411/test-0036-report-multi-alt-for-one-ref.sh: test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 1st r\n    + \tremote: # post-receive hook\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    + \tTo <URL/of/upstream.git>\n    +-\t * [new reference] HEAD -> refs/for/master/topic\n    ++\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n    ++\t * [new reference] HEAD -> refs/changes/24/124/1\n    ++\t <OID-A>..<OID-B> HEAD -> refs/changes/25/125/1\n    + \tEOF\n    + \ttest_cmp expect actual &&\n      \tgit -C \"$upstream\" show-ref >out &&\n    -@@ t/t5411/common-test-cases.sh: test_expect_success \"proc-receive: report multiple response\" '\n    +@@ t/t5411/test-0036-report-multi-alt-for-one-ref.sh: test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 2nd r\n    + \tremote: # post-receive hook\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    + \tTo <URL/of/upstream.git>\n    +-\t * [new reference] HEAD -> refs/for/master/topic\n    ++\t * [new reference] HEAD -> refs/changes/24/124/1\n    ++\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n    ++\t + <OID-B>...<OID-A> HEAD -> refs/changes/25/125/1 (forced update)\n    + \tEOF\n    + \ttest_cmp expect actual &&\n    + \tgit -C \"$upstream\" show-ref >out &&\n    +@@ t/t5411/test-0036-report-multi-alt-for-one-ref.sh: test_expect_success \"proc-receive: report ok and alt for the same ref ($PROTOCOL\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    + \tTo <URL/of/upstream.git>\n    + \t * [new reference] HEAD -> refs/for/master/topic\n    ++\t <OID-A>..<OID-B> HEAD -> refs/changes/24/124/1\n    + \tEOF\n    + \ttest_cmp expect actual &&\n    + \tgit -C \"$upstream\" show-ref >out &&\n    +@@ t/t5411/test-0036-report-multi-alt-for-one-ref.sh: test_expect_success \"proc-receive: report multiple response ($PROTOCOL)\" '\n      \tremote: # post-receive hook\n      \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n      \tTo <URL/of/upstream.git>\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"proc-receive: report multiple\n      \tEOF\n      \ttest_cmp expect actual &&\n      \tgit -C \"$upstream\" show-ref >out &&\n    -@@ t/t5411/common-test-cases.sh: test_expect_success \"proc-receive: report update of mixed refs\" '\n    +\n    + ## t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh ##\n    +@@ t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh: test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 1st r\n    + \tremote: # post-receive hook\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    + \tTo <URL/of/upstream.git>\n    +-\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n    ++\t*    HEAD:refs/changes/24/124/1    [new reference]\n    ++\t     HEAD:refs/changes/25/125/1    <OID-A>..<OID-B>\n    + \tDone\n    + \tEOF\n    + \ttest_cmp expect actual &&\n    +@@ t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh: test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 2nd r\n    + \tremote: # post-receive hook\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    + \tTo <URL/of/upstream.git>\n    +-\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\t*    HEAD:refs/changes/24/124/1    [new reference]\n    ++\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n    ++\t+    HEAD:refs/changes/25/125/1    <OID-B>...<OID-A> (forced update)\n    + \tDone\n    + \tEOF\n    + \ttest_cmp expect actual &&\n    +@@ t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh: test_expect_success \"proc-receive: report ok and alt for the same ref (--porcela\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    + \tTo <URL/of/upstream.git>\n    + \t*    HEAD:refs/for/master/topic    [new reference]\n    ++\t     HEAD:refs/changes/24/124/1    <OID-A>..<OID-B>\n    + \tDone\n    + \tEOF\n    + \ttest_cmp expect actual &&\n    +@@ t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh: test_expect_success \"proc-receive: report multiple response (--porcelain, $PROTO\n    + \tremote: # post-receive hook\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    + \tTo <URL/of/upstream.git>\n    +-\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\t*    HEAD:refs/changes/23/123/1    [new reference]\n    ++\t     HEAD:refs/changes/24/124/2    <OID-A>..<OID-B>\n    + \tDone\n    + \tEOF\n    + \ttest_cmp expect actual &&\n    +\n    + ## t/t5411/test-0038-report-mixed-refs.sh ##\n    +@@ t/t5411/test-0038-report-mixed-refs.sh: test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n      \t * [new branch] HEAD -> baz\n      \t * [new reference] HEAD -> refs/for/next/topic\n      \t * [new branch] HEAD -> foo\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"proc-receive: report update o\n      \ttest_cmp expect actual &&\n      \tgit -C \"$upstream\" show-ref >out &&\n     \n    + ## t/t5411/test-0039-report-mixed-refs--porcelain.sh ##\n    +@@ t/t5411/test-0039-report-mixed-refs--porcelain.sh: test_expect_success \"proc-receive: report update of mixed refs (--porcelain, $PR\n    + \t*    HEAD:refs/heads/baz    [new branch]\n    + \t*    HEAD:refs/for/next/topic    [new reference]\n    + \t*    HEAD:refs/heads/foo    [new branch]\n    +-\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n    + \tDone\n    + \tEOF\n    + \ttest_cmp expect actual &&\n    +\n      ## transport-helper.c ##\n     @@ transport-helper.c: static int push_update_ref_status(struct strbuf *buf,\n      \t\t\tmsg = xstrdup(msg);\n    @@ transport.c: static void print_ref_status(char flag, const char *summary,\n     +\tif (to && to->remote_status) {\n     +\t\tval = parse_feature_value(to->remote_status, \"ref\", &len);\n     +\t\tif (val && len)\n    -+\t\tto_name = xmemdupz(val, len);\n    ++\t\t\tto_name = xmemdupz(val, len);\n     +\t}\n     +\n      \tif (porcelain) {\n    @@ transport.c: static int print_one_push_status(struct ref *ref, const char *dest,\n     +\t\t\t\t\t      porcelain, summary_width);\n     +\n     +\thead = ref->remote_status;\n    -+\tbegin = strstr(head, \"ref=\");\n    -+\tif (!begin)\n    -+\t\tbegin = ref->remote_status;\n    ++\tbegin = head;\n     +\tfor (;;) {\n     +\t\tchar *end;\n     +\t\tstruct object_id old_oid;\n-:  ---------- > 5:  c3ad1ab5a4 receive-pack: feed extended_status to post-receive\n5:  a8860faa17 = 6:  d9226a4898 refs.c: refactor to reuse ref_is_hidden()\n6:  46a839ddf6 ! 7:  7143e2c501 receive-pack: new config receive.procReceiveRefs\n    @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n     +\tif (proc_receive_refs.nr > 0) {\n     +\t\tstruct strbuf refname_full = STRBUF_INIT;\n     +\t\tsize_t prefix_len;\n    -+\n    -+\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n    -+\t\tprefix_len = refname_full.len;\n      \n     -\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n     -\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n     -\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n     -\t\t\trun_proc_receive = 1;\n    ++\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n    ++\t\tprefix_len = refname_full.len;\n    ++\n     +\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n     +\t\t\tif (!should_process_cmd(cmd))\n     +\t\t\t\tcontinue;\n    @@ builtin/receive-pack.c: int cmd_receive_pack(int argc, const char **argv, const\n      \treturn 0;\n      }\n     \n    - ## t/t5411/common-test-cases.sh ##\n    -@@ t/t5411/common-test-cases.sh: test_expect_success \"cleanup\" '\n    - \t)\n    - '\n    - \n    + ## t/t5411/test-0010-proc-receive-settings.sh (new) ##\n    +@@\n     +test_expect_success \"add two receive.procReceiveRefs settings\" '\n     +\t(\n     +\t\tcd \"$upstream\" &&\n     +\t\tgit config --add receive.procReceiveRefs refs/for &&\n     +\t\tgit config --add receive.procReceiveRefs refs/review/\n     +\t)\n    ++'\n    +\n    + ## t/t5411/test-0040-process-all-refs.sh (new) ##\n    +@@\n    ++test_expect_success \"config receive.procReceiveRefs = refs ($PROTOCOL)\" '\n    ++\tgit -C \"$upstream\" config --unset-all receive.procReceiveRefs &&\n    ++\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs\n     +'\n     +\n    - # Refs of upstream : master(A)\n    - # Refs of workbench: master(A)  tags/v123\n    - # git push         :                       next(A)  refs/for/master/topic(A)\n    -@@ t/t5411/common-test-cases.sh: test_expect_success \"setup proc-receive hook (with extended status)\" '\n    - \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    - \tprintf >&2 \"# proc-receive hook\\n\"\n    - \ttest-tool proc-receive -v \\\n    --\t\t-r \"ok refs/for/a/b/c/topic\" \\\n    -+\t\t-r \"ok refs/review/a/b/c/topic\" \\\n    - \t\t-r \"alt refs/for/next/topic refs/pull/123/head\" \\\n    - \t\t-r \"alt refs/for/master/topic refs/pull/124/head old-oid=$B forced-update new-oid=$A\"\n    - \tEOF\n    -@@ t/t5411/common-test-cases.sh: test_expect_success \"setup proc-receive hook (with extended status)\" '\n    - \n    - # Refs of upstream : master(A)\n    - # Refs of workbench: master(A)  tags/v123\n    --# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    -+# git push         :                       refs/for/next/topic(A)  refs/review/a/b/c/topic(A)  refs/for/master/topic(A)\n    - test_expect_success \"proc-receive: report with extended status\" '\n    - \tgit -C workbench push origin \\\n    - \t\tHEAD:refs/for/next/topic \\\n    --\t\tHEAD:refs/for/a/b/c/topic \\\n    -+\t\tHEAD:refs/review/a/b/c/topic \\\n    - \t\tHEAD:refs/for/master/topic \\\n    - \t\t>out 2>&1 &&\n    - \tmake_user_friendly_and_stable_output <out >actual &&\n    - \tcat >expect <<-EOF &&\n    - \tremote: # pre-receive hook\n    - \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    --\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n    - \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    - \tremote: # proc-receive hook\n    - \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    --\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n    -+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n    - \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    --\tremote: proc-receive> ok refs/for/a/b/c/topic\n    -+\tremote: proc-receive> ok refs/review/a/b/c/topic\n    - \tremote: proc-receive> alt refs/for/next/topic refs/pull/123/head\n    - \tremote: proc-receive> alt refs/for/master/topic refs/pull/124/head old-oid=<COMMIT-B> forced-update new-oid=<COMMIT-A>\n    - \tremote: # post-receive hook\n    - \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    --\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/a/b/c/topic\n    - \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    - \tTo <URL/of/upstream.git>\n    - \t * [new reference] HEAD -> refs/pull/123/head\n    --\t * [new reference] HEAD -> refs/for/a/b/c/topic\n    -+\t * [new reference] HEAD -> refs/review/a/b/c/topic\n    - \t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n    - \tEOF\n    - \ttest_cmp expect actual &&\n    -@@ t/t5411/common-test-cases.sh: test_expect_success \"proc-receive: report update of mixed refs\" '\n    - \tEOF\n    - \ttest_cmp expect actual\n    - '\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++test_expect_success \"setup upstream branches ($PROTOCOL)\" '\n    ++\t(\n    ++\t\tcd \"$upstream\" &&\n    ++\t\tgit update-ref refs/heads/master $B &&\n    ++\t\tgit update-ref refs/heads/foo $A &&\n    ++\t\tgit update-ref refs/heads/bar $A &&\n    ++\t\tgit update-ref refs/heads/baz $A\n    ++\t)\n     +\n    -+test_expect_success \"config add receive.procReceiveRefs = refs\" '\n    -+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook\" '\n    ++test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"proc-receive: report update o\n     +# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n    -+test_expect_success \"proc-receive: process all refs\" '\n    ++test_expect_success \"proc-receive: process all refs ($PROTOCOL)\" '\n     +\tgit -C workbench push -f origin \\\n     +\t\tHEAD:refs/heads/master \\\n     +\t\t:refs/heads/foo \\\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"proc-receive: report update o\n     +\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n     +\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n     +\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head\n    ++\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n     +\tTo <URL/of/upstream.git>\n     +\t <OID-A>..<OID-B> <COMMIT-B> -> bar\n     +\t - [deleted] foo\n    @@ t/t5411/common-test-cases.sh: test_expect_success \"proc-receive: report update o\n     +\tEOF\n     +\ttest_cmp expect actual\n     +'\n    ++\n    ++# Refs of upstream : master(A)             bar(A)  baz(B)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++test_expect_success \"cleanup ($PROTOCOL)\" '\n    ++\t(\n    ++\t\tcd \"$upstream\" &&\n    ++\t\tgit update-ref -d refs/heads/bar &&\n    ++\t\tgit update-ref -d refs/heads/baz\n    ++\t)\n    ++'\n    +\n    + ## t/t5411/test-0041-process-all-refs--porcelain.sh (new) ##\n    +@@\n    ++test_expect_success \"config receive.procReceiveRefs = refs ($PROTOCOL)\" '\n    ++\tgit -C \"$upstream\" config --unset-all receive.procReceiveRefs &&\n    ++\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++test_expect_success \"setup upstream branches ($PROTOCOL)\" '\n    ++\t(\n    ++\t\tcd \"$upstream\" &&\n    ++\t\tgit update-ref refs/heads/master $B &&\n    ++\t\tgit update-ref refs/heads/foo $A &&\n    ++\t\tgit update-ref refs/heads/bar $A &&\n    ++\t\tgit update-ref refs/heads/baz $A\n    ++\t)\n    ++\n    ++'\n    ++\n    ++test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"ft refs/heads/master\" \\\n    ++\t\t-r \"ft refs/heads/foo\" \\\n    ++\t\t-r \"ft refs/heads/bar\" \\\n    ++\t\t-r \"alt refs/for/master/topic refs/pull/123/head old-oid=$A new-oid=$B \" \\\n    ++\t\t-r \"alt refs/for/next/topic refs/pull/124/head old-oid=$B new-oid=$A forced-update\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n    ++test_expect_success \"proc-receive: process all refs (--porcelain) ($PROTOCOL)\" '\n    ++\tgit -C workbench push --porcelain -f origin \\\n    ++\t\tHEAD:refs/heads/master \\\n    ++\t\t:refs/heads/foo \\\n    ++\t\t$B:refs/heads/bar \\\n    ++\t\tHEAD:refs/for/master/topic \\\n    ++\t\tHEAD:refs/for/next/topic \\\n    ++\t\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\tremote: # pre-receive hook\n    ++\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n    ++\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n    ++\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: # proc-receive hook\n    ++\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n    ++\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n    ++\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: proc-receive> ft refs/heads/master\n    ++\tremote: proc-receive> ft refs/heads/foo\n    ++\tremote: proc-receive> ft refs/heads/bar\n    ++\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    ++\tremote: proc-receive> alt refs/for/next/topic refs/pull/124/head old-oid=<COMMIT-B> new-oid=<COMMIT-A> forced-update\n    ++\tremote: # post-receive hook\n    ++\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n    ++\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n    ++\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n    ++\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head\n    ++\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n    ++\tTo <URL/of/upstream.git>\n    ++\t     <COMMIT-B>:refs/heads/bar    <OID-A>..<OID-B>\n    ++\t-    :refs/heads/foo    [deleted]\n    ++\t+    HEAD:refs/heads/master    <OID-B>...<OID-A> (forced update)\n    ++\t     HEAD:refs/pull/123/head    <OID-A>..<OID-B>\n    ++\t+    HEAD:refs/pull/124/head    <OID-B>...<OID-A> (forced update)\n    ++\tDone\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-B> refs/heads/bar\n    ++\t<COMMIT-A> refs/heads/baz\n    ++\t<COMMIT-A> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++# Refs of upstream : master(A)             bar(A)  baz(B)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++test_expect_success \"cleanup ($PROTOCOL)\" '\n    ++\t(\n    ++\t\tcd \"$upstream\" &&\n    ++\t\tgit update-ref -d refs/heads/bar &&\n    ++\t\tgit update-ref -d refs/heads/baz\n    ++\t)\n    ++'\n7:  a357234e97 = 8:  98702cf3e9 doc: add documentation for the proc-receive hook\n\n---\n\nJiang Xin (8):\n  transport: not report a non-head push as a branch\n  connect: export parse_feature_value()\n  receive-pack: add new proc-receive hook\n  send-pack: extension for client-side status report\n  receive-pack: feed extended_status to post-receive\n  refs.c: refactor to reuse ref_is_hidden()\n  receive-pack: new config receive.procReceiveRefs\n  doc: add documentation for the proc-receive hook\n\n Documentation/config/receive.txt              |  14 +\n Documentation/githooks.txt                    |  58 +++\n Makefile                                      |   1 +\n builtin/receive-pack.c                        | 442 +++++++++++++++++-\n connect.c                                     |   3 +-\n connect.h                                     |   1 +\n refs.c                                        |  13 +-\n refs.h                                        |   1 +\n remote.c                                      |  21 +\n remote.h                                      |   3 +\n send-pack.c                                   |  11 +-\n t/helper/test-proc-receive.c                  | 175 +++++++\n t/helper/test-tool.c                          |   1 +\n t/helper/test-tool.h                          |   1 +\n t/t5411-proc-receive-hook.sh                  | 105 +++++\n t/t5411/common-functions.sh                   |  56 +++\n t/t5411/test-0000-standard-git-push.sh        |  84 ++++\n .../test-0001-standard-git-push--porcelain.sh |  87 ++++\n t/t5411/test-0002-pre-receive-declined.sh     |  33 ++\n ...st-0003-pre-receive-declined--porcelain.sh |  34 ++\n t/t5411/test-0010-proc-receive-settings.sh    |   7 +\n t/t5411/test-0011-no-hook-error.sh            |  64 +++\n t/t5411/test-0012-no-hook-error--porcelain.sh |  66 +++\n t/t5411/test-0013-bad-protocol.sh             | 225 +++++++++\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 160 +++++++\n t/t5411/test-0020-report-ng.sh                |  67 +++\n t/t5411/test-0021-report-ng--porcelain.sh     |  69 +++\n t/t5411/test-0022-report-unexpect-ref.sh      |  45 ++\n ...est-0023-report-unexpect-ref--porcelain.sh |  46 ++\n t/t5411/test-0024-report-unknown-ref.sh       |  34 ++\n ...test-0025-report-unknown-ref--porcelain.sh |  35 ++\n t/t5411/test-0026-push-options.sh             |  79 ++++\n t/t5411/test-0027-push-options--porcelain.sh  |  82 ++++\n t/t5411/test-0030-report-ok.sh                |  35 ++\n t/t5411/test-0031-report-ok--porcelain.sh     |  36 ++\n t/t5411/test-0032-report-alt.sh               | 229 +++++++++\n t/t5411/test-0033-report-alt--porcelain.sh    | 236 ++++++++++\n t/t5411/test-0034-report-ft.sh                |  42 ++\n t/t5411/test-0035-report-ft--porcelain.sh     |  43 ++\n .../test-0036-report-multi-alt-for-one-ref.sh | 167 +++++++\n ...report-multi-alt-for-one-ref--porcelain.sh | 171 +++++++\n t/t5411/test-0038-report-mixed-refs.sh        |  74 +++\n .../test-0039-report-mixed-refs--porcelain.sh |  75 +++\n t/t5411/test-0040-process-all-refs.sh         |  93 ++++\n .../test-0041-process-all-refs--porcelain.sh  |  94 ++++\n t/t5516-fetch-push.sh                         |   2 +-\n transport-helper.c                            |  64 +--\n transport.c                                   |  83 +++-\n 48 files changed, 3506 insertions(+), 61 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/test-0000-standard-git-push.sh\n create mode 100644 t/t5411/test-0001-standard-git-push--porcelain.sh\n create mode 100644 t/t5411/test-0002-pre-receive-declined.sh\n create mode 100644 t/t5411/test-0003-pre-receive-declined--porcelain.sh\n create mode 100644 t/t5411/test-0010-proc-receive-settings.sh\n create mode 100644 t/t5411/test-0011-no-hook-error.sh\n create mode 100644 t/t5411/test-0012-no-hook-error--porcelain.sh\n create mode 100644 t/t5411/test-0013-bad-protocol.sh\n create mode 100644 t/t5411/test-0014-bad-protocol--porcelain.sh\n create mode 100644 t/t5411/test-0020-report-ng.sh\n create mode 100644 t/t5411/test-0021-report-ng--porcelain.sh\n create mode 100644 t/t5411/test-0022-report-unexpect-ref.sh\n create mode 100644 t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n create mode 100644 t/t5411/test-0024-report-unknown-ref.sh\n create mode 100644 t/t5411/test-0025-report-unknown-ref--porcelain.sh\n create mode 100644 t/t5411/test-0026-push-options.sh\n create mode 100644 t/t5411/test-0027-push-options--porcelain.sh\n create mode 100644 t/t5411/test-0030-report-ok.sh\n create mode 100644 t/t5411/test-0031-report-ok--porcelain.sh\n create mode 100644 t/t5411/test-0032-report-alt.sh\n create mode 100644 t/t5411/test-0033-report-alt--porcelain.sh\n create mode 100644 t/t5411/test-0034-report-ft.sh\n create mode 100644 t/t5411/test-0035-report-ft--porcelain.sh\n create mode 100644 t/t5411/test-0036-report-multi-alt-for-one-ref.sh\n create mode 100644 t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh\n create mode 100644 t/t5411/test-0038-report-mixed-refs.sh\n create mode 100644 t/t5411/test-0039-report-mixed-refs--porcelain.sh\n create mode 100644 t/t5411/test-0040-process-all-refs.sh\n create mode 100644 t/t5411/test-0041-process-all-refs--porcelain.sh\n\n-- \n\n"},{"id":"395688","messageId":"20200418160334.15631-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200414123257.27449-1-worldhello.net@gmail.com","subject":"[PATCH v13 2/8] connect: export parse_feature_value()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-18T16:03:28Z","receivedAt":"2020-04-18T16:03:58Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nExport `parse_feature_value()` to parse extended status of report for\n\"proc-receive\" and \"receive-pack\".\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n connect.c | 3 +--\n connect.h | 1 +\n 2 files changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/connect.c b/connect.c\nindex b6451ab5e8..08e0616726 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -18,7 +18,6 @@\n \n static char *server_capabilities_v1;\n static struct argv_array server_capabilities_v2 = ARGV_ARRAY_INIT;\n-static const char *parse_feature_value(const char *, const char *, int *);\n \n static int check_ref(const char *name, unsigned int flags)\n {\n@@ -447,7 +446,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \treturn list;\n }\n \n-static const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp)\n+const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp)\n {\n \tint len;\n \ndiff --git a/connect.h b/connect.h\nindex 5f2382e018..17b3252cbc 100644\n--- a/connect.h\n+++ b/connect.h\n@@ -12,6 +12,7 @@ int finish_connect(struct child_process *conn);\n int git_connection_is_socket(struct child_process *conn);\n int server_supports(const char *feature);\n int parse_feature_request(const char *features, const char *feature);\n+const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret);\n const char *server_feature_value(const char *feature, int *len_ret);\n int url_is_local_not_ssh(const char *url);\n \n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395685","messageId":"20200418160334.15631-5-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200414123257.27449-1-worldhello.net@gmail.com","subject":"[PATCH v13 4/8] send-pack: extension for client-side status report","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-18T16:03:30Z","receivedAt":"2020-04-18T16:03:59Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThis is the client-side extension for status report of git-push.\n\nThe \"proc-receive\" hook may receive a command for a pseudo-reference\nwith a zero-old as its old-oid, while the result of the hook may point\nto an alternate reference and the reference may exist already with a\nnon-zero old-oid.  Git client may receive a report with extened status\nas the following example:\n\n    ok refs/for/master/topic\\0ref=refs/pull/123/head old-oid=...\n\nParse the key-value pairs in the extended status after the null\ncharacter of the report, and show the proper reference name, old-oid\nand new-oid to the user.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n remote.c                                      | 21 ++++++\n remote.h                                      |  3 +\n send-pack.c                                   | 11 ++-\n t/t5411/test-0032-report-alt.sh               | 14 ++--\n t/t5411/test-0033-report-alt--porcelain.sh    | 14 ++--\n .../test-0036-report-multi-alt-for-one-ref.sh | 12 ++-\n ...report-multi-alt-for-one-ref--porcelain.sh | 12 ++-\n t/t5411/test-0038-report-mixed-refs.sh        |  2 +-\n .../test-0039-report-mixed-refs--porcelain.sh |  2 +-\n transport-helper.c                            | 64 ++++++++--------\n transport.c                                   | 74 +++++++++++++++++--\n 11 files changed, 169 insertions(+), 60 deletions(-)\n\ndiff --git a/remote.c b/remote.c\nindex c43196ec06..4174746bb9 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"config.h\"\n+#include \"connect.h\"\n #include \"remote.h\"\n #include \"refs.h\"\n #include \"refspec.h\"\n@@ -2311,3 +2312,23 @@ void apply_push_cas(struct push_cas_option *cas,\n \tfor (ref = remote_refs; ref; ref = ref->next)\n \t\tapply_cas(cas, remote, ref);\n }\n+\n+void update_ref_from_remote_status(struct ref *ref)\n+{\n+\tchar *val;\n+\tint len;\n+\n+\tif (!ref->remote_status)\n+\t\treturn;\n+\n+\tval = (char *)parse_feature_value(ref->remote_status, \"old-oid\", &len);\n+\tif (val && len)\n+\t\tget_oid_hex(val, &ref->old_oid);\n+\n+\tval = (char *)parse_feature_value(ref->remote_status, \"new-oid\", &len);\n+\tif (val && len)\n+\t\tget_oid_hex(val, &ref->new_oid);\n+\n+\tif (parse_feature_request(ref->remote_status, \"forced-update\"))\n+\t\tref->forced_update = 1;\n+}\ndiff --git a/remote.h b/remote.h\nindex 11d8719b58..09cf7014e3 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -345,4 +345,7 @@ int parseopt_push_cas_option(const struct option *, const char *arg, int unset);\n int is_empty_cas(const struct push_cas_option *);\n void apply_push_cas(struct push_cas_option *, struct remote *, struct ref *);\n \n+/* Parse key-value pairs of remote_status and update the reference accordingly */\n+void update_ref_from_remote_status(struct ref *ref);\n+\n #endif\ndiff --git a/send-pack.c b/send-pack.c\nindex a7c53193c9..6e2c7a0c0c 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -159,6 +159,9 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \twhile (1) {\n \t\tconst char *refname;\n \t\tchar *msg;\n+\t\tchar *extended_status = NULL;\n+\t\tint len;\n+\n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n \t\t\tbreak;\n \t\tif (!starts_with(reader->line, \"ok \") && !starts_with(reader->line, \"ng \")) {\n@@ -167,10 +170,13 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \t\t\tbreak;\n \t\t}\n \n+\t\tlen = strlen(reader->line);\n \t\trefname = reader->line + 3;\n \t\tmsg = strchr(refname, ' ');\n \t\tif (msg)\n \t\t\t*msg++ = '\\0';\n+\t\tif (reader->pktlen > len)\n+\t\t\textended_status = (char *)reader->line + len + 1;\n \n \t\t/* first try searching at our hint, falling back to all refs */\n \t\tif (hint)\n@@ -192,7 +198,10 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \t\t\thint->status = REF_STATUS_OK;\n \t\telse\n \t\t\thint->status = REF_STATUS_REMOTE_REJECT;\n-\t\thint->remote_status = xstrdup_or_null(msg);\n+\t\tif (msg)\n+\t\t\thint->remote_status = xstrdup(msg);\n+\t\telse if (extended_status)\n+\t\t\thint->remote_status = xstrdup_or_null(extended_status);\n \t\t/* start our next search from the next ref */\n \t\thint = hint->next;\n \t}\ndiff --git a/t/t5411/test-0032-report-alt.sh b/t/t5411/test-0032-report-alt.sh\nindex dec4afc4c1..bf5faf4f2e 100644\n--- a/t/t5411/test-0032-report-alt.sh\n+++ b/t/t5411/test-0032-report-alt.sh\n@@ -52,7 +52,7 @@ test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref>, $PROTOCOL)\"\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -82,7 +82,7 @@ test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> forced-update\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -112,7 +112,7 @@ test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> old-oid=X, $P\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-B>..<OID-A> HEAD -> refs/pull/123/head\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -142,7 +142,7 @@ test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X, $PROTOCOL)\"\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-B>..<OID-A> HEAD -> refs/for/master/topic\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -172,7 +172,7 @@ test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X new-oid=Y, $P\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -214,9 +214,9 @@ test_expect_success \"proc-receive: with multiple alt reports ($PROTOCOL)\" '\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \t * [new reference] HEAD -> refs/for/a/b/c/topic\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n \ndiff --git a/t/t5411/test-0033-report-alt--porcelain.sh b/t/t5411/test-0033-report-alt--porcelain.sh\nindex 49d76892ab..3aea911407 100644\n--- a/t/t5411/test-0033-report-alt--porcelain.sh\n+++ b/t/t5411/test-0033-report-alt--porcelain.sh\n@@ -53,7 +53,7 @@ test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref>, --porcelain,\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -84,7 +84,7 @@ test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> forced-update\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -115,7 +115,7 @@ test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> old-oid=X, --\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/pull/123/head    <OID-B>..<OID-A>\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -146,7 +146,7 @@ test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X, --porcelain,\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-B>..<OID-A>\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -177,7 +177,7 @@ test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X new-oid=Y, --\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -220,9 +220,9 @@ test_expect_success \"proc-receive: with multiple alt reports (--porcelain, $PROT\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/next/topic    [new reference]\n+\t*    HEAD:refs/pull/123/head    [new reference]\n \t*    HEAD:refs/for/a/b/c/topic    [new reference]\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t+    HEAD:refs/pull/124/head    <OID-B>...<OID-A> (forced update)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\ndiff --git a/t/t5411/test-0036-report-multi-alt-for-one-ref.sh b/t/t5411/test-0036-report-multi-alt-for-one-ref.sh\nindex fc019f709b..d82b79ffff 100644\n--- a/t/t5411/test-0036-report-multi-alt-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-alt-for-one-ref.sh\n@@ -27,7 +27,9 @@ test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 1st r\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/24/124/1\n+\t <OID-A>..<OID-B> HEAD -> refs/changes/25/125/1\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -67,7 +69,9 @@ test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 2nd r\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/24/124/1\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n+\t + <OID-B>...<OID-A> HEAD -> refs/changes/25/125/1 (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -106,6 +110,7 @@ test_expect_success \"proc-receive: report ok and alt for the same ref ($PROTOCOL\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/changes/24/124/1\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -143,7 +148,8 @@ test_expect_success \"proc-receive: report multiple response ($PROTOCOL)\" '\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/23/123/1\n+\t <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\ndiff --git a/t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh\nindex 22607b135e..8cca63c2de 100644\n--- a/t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh\n+++ b/t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh\n@@ -27,7 +27,9 @@ test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 1st r\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n+\t*    HEAD:refs/changes/24/124/1    [new reference]\n+\t     HEAD:refs/changes/25/125/1    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n@@ -68,7 +70,9 @@ test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 2nd r\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/changes/24/124/1    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n+\t+    HEAD:refs/changes/25/125/1    <OID-B>...<OID-A> (forced update)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n@@ -108,6 +112,7 @@ test_expect_success \"proc-receive: report ok and alt for the same ref (--porcela\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/changes/24/124/1    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n@@ -146,7 +151,8 @@ test_expect_success \"proc-receive: report multiple response (--porcelain, $PROTO\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/changes/23/123/1    [new reference]\n+\t     HEAD:refs/changes/24/124/2    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nindex dd03bf1538..566cb6e098 100644\n--- a/t/t5411/test-0038-report-mixed-refs.sh\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -46,7 +46,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n \t * [new branch] HEAD -> baz\n \t * [new reference] HEAD -> refs/for/next/topic\n \t * [new branch] HEAD -> foo\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nindex e237f40db7..4d617abb1a 100644\n--- a/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -46,7 +46,7 @@ test_expect_success \"proc-receive: report update of mixed refs (--porcelain, $PR\n \t*    HEAD:refs/heads/baz    [new branch]\n \t*    HEAD:refs/for/next/topic    [new reference]\n \t*    HEAD:refs/heads/foo    [new branch]\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\ndiff --git a/transport-helper.c b/transport-helper.c\nindex a46afcb69d..0c835c5495 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -747,37 +747,39 @@ static int push_update_ref_status(struct strbuf *buf,\n \t\t\tmsg = xstrdup(msg);\n \t\tstrbuf_release(&msg_buf);\n \n-\t\tif (!strcmp(msg, \"no match\")) {\n-\t\t\tstatus = REF_STATUS_NONE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"up to date\")) {\n-\t\t\tstatus = REF_STATUS_UPTODATE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"non-fast forward\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_NONFASTFORWARD;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"already exists\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_ALREADY_EXISTS;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"fetch first\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_FETCH_FIRST;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"needs force\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_NEEDS_FORCE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"stale info\")) {\n-\t\t\tstatus = REF_STATUS_REJECT_STALE;\n-\t\t\tFREE_AND_NULL(msg);\n-\t\t}\n-\t\telse if (!strcmp(msg, \"forced update\")) {\n-\t\t\tforced = 1;\n-\t\t\tFREE_AND_NULL(msg);\n+\t\tif (status != REF_STATUS_OK) {\n+\t\t\tif (!strcmp(msg, \"no match\")) {\n+\t\t\t\tstatus = REF_STATUS_NONE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"up to date\")) {\n+\t\t\t\tstatus = REF_STATUS_UPTODATE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"non-fast forward\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_NONFASTFORWARD;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"already exists\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_ALREADY_EXISTS;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"fetch first\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_FETCH_FIRST;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"needs force\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_NEEDS_FORCE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"stale info\")) {\n+\t\t\t\tstatus = REF_STATUS_REJECT_STALE;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n+\t\t\telse if (!strcmp(msg, \"forced update\")) {\n+\t\t\t\tforced = 1;\n+\t\t\t\tFREE_AND_NULL(msg);\n+\t\t\t}\n \t\t}\n \t}\n \ndiff --git a/transport.c b/transport.c\nindex 73defc4a5a..ecdcaae90d 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -463,11 +463,23 @@ static void print_ref_status(char flag, const char *summary,\n \t\t\t     struct ref *to, struct ref *from, const char *msg,\n \t\t\t     int porcelain, int summary_width)\n {\n+\tchar *to_name = NULL;\n+\tconst char *val;\n+\tint len;\n+\n+\tif (to && to->remote_status) {\n+\t\tval = parse_feature_value(to->remote_status, \"ref\", &len);\n+\t\tif (val && len)\n+\t\t\tto_name = xmemdupz(val, len);\n+\t}\n+\n \tif (porcelain) {\n \t\tif (from)\n-\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name,\n+\t\t\t\tto_name ? to_name : to->name);\n \t\telse\n-\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag,\n+\t\t\t\tto_name ? to_name : to->name);\n \t\tif (msg)\n \t\t\tfprintf(stdout, \"%s (%s)\\n\", summary, msg);\n \t\telse\n@@ -481,9 +493,11 @@ static void print_ref_status(char flag, const char *summary,\n \t\tfprintf(stderr, \" %s%c %-*s%s \", red, flag, summary_width,\n \t\t\tsummary, reset);\n \t\tif (from)\n-\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(to->name));\n+\t\t\tfprintf(stderr, \"%s -> %s\",\n+\t\t\t\tprettify_refname(from->name),\n+\t\t\t\tprettify_refname(to_name ? to_name : to->name));\n \t\telse\n-\t\t\tfputs(prettify_refname(to->name), stderr);\n+\t\t\tfputs(prettify_refname(to_name ? to_name : to->name), stderr);\n \t\tif (msg) {\n \t\t\tfputs(\" (\", stderr);\n \t\t\tfputs(msg, stderr);\n@@ -491,6 +505,7 @@ static void print_ref_status(char flag, const char *summary,\n \t\t}\n \t\tfputc('\\n', stderr);\n \t}\n+\tfree(to_name);\n }\n \n static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_width)\n@@ -531,8 +546,8 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t}\n }\n \n-static int print_one_push_status(struct ref *ref, const char *dest, int count,\n-\t\t\t\t int porcelain, int summary_width)\n+static int _print_one_push_status(struct ref *ref, const char *dest, int count,\n+\t\t\t\t  int porcelain, int summary_width)\n {\n \tif (!count) {\n \t\tchar *url = transport_anonymize_url(dest);\n@@ -602,6 +617,53 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count,\n \treturn 1;\n }\n \n+static int print_one_push_status(struct ref *ref, const char *dest, int count,\n+\t\t\t\t int porcelain, int summary_width)\n+{\n+\tchar *head;\n+\tchar *begin;\n+\tint n = 0;\n+\n+\tif (!ref->remote_status)\n+\t\treturn _print_one_push_status(ref, dest, count,\n+\t\t\t\t\t      porcelain, summary_width);\n+\n+\thead = ref->remote_status;\n+\tbegin = head;\n+\tfor (;;) {\n+\t\tchar *end;\n+\t\tstruct object_id old_oid;\n+\t\tstruct object_id new_oid;\n+\t\tint forced_update;\n+\n+\t\tend = strstr(begin + 4, \"ref=\");\n+\t\tif (end)\n+\t\t\t*(end-1) = '\\0';\n+\n+\t\toidcpy(&old_oid, &ref->old_oid);\n+\t\toidcpy(&new_oid, &ref->new_oid);\n+\t\tforced_update = ref->forced_update;\n+\n+\t\tref->remote_status = begin;\n+\t\tupdate_ref_from_remote_status(ref);\n+\t\t_print_one_push_status(ref, dest, count + n++,\n+\t\t\t\t       porcelain, summary_width);\n+\n+\t\toidcpy(&ref->old_oid, &old_oid);\n+\t\toidcpy(&ref->new_oid, &new_oid);\n+\t\tref->forced_update = forced_update;\n+\n+\t\tif (end) {\n+\t\t\tbegin = end;\n+\t\t\t*(end-1) = ' ';\n+\t\t} else {\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\tref->remote_status = head;\n+\treturn n;\n+}\n+\n static int measure_abbrev(const struct object_id *oid, int sofar)\n {\n \tchar hex[GIT_MAX_HEXSZ + 1];\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395684","messageId":"20200418160334.15631-9-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200414123257.27449-1-worldhello.net@gmail.com","subject":"[PATCH v13 8/8] doc: add documentation for the proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-18T16:03:34Z","receivedAt":"2020-04-18T16:04:00Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd documentation for the new \"proc-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/githooks.txt | 58 ++++++++++++++++++++++++++++++++++++++\n 1 file changed, 58 insertions(+)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 3dccab5375..a0ea829435 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -333,6 +333,64 @@ The default 'update' hook, when enabled--and with\n `hooks.allowunannotated` config option unset or set to false--prevents\n unannotated tags to be pushed.\n \n+proc-receive\n+~~~~~~~~~~~~\n+\n+This hook is invoked by linkgit:git-receive-pack[1].  If the server has\n+set the multi-valued config variable `receive.procReceiveRefs`, and the\n+commands sent to 'receive-pack' have matching reference names, these\n+commands will be executed by this hook, instead of by the internal\n+`execute_commands()` function.  This hook is responsible for updating\n+the relevant references and reporting the results back to 'receive-pack'.\n+\n+This hook executes once for the receive operation.  It takes no\n+arguments, but uses a pkt-line format protocol to communicate with\n+'receive-pack' to read commands, push-options and send results.  In the\n+following example for the protocol, the letter 'S' stands for\n+'receive-pack' and the letter 'H' stands for this hook.\n+\n+    # Version and features negotiation.\n+    S: PKT-LINE(version=1\\0push-options atomic...)\n+    S: flush-pkt\n+    H: PKT-LINE(version=1\\0push-options...)\n+    H: flush-pkt\n+\n+    # Send commands from server to the hook.\n+    S: PKT-LINE(<old-oid> <new-oid> <ref>)\n+    S: ... ...\n+    S: flush-pkt\n+    # Send push-options only if the 'push-options' feature is enabled.\n+    S: PKT-LINE(push-option)\n+    S: ... ...\n+    S: flush-pkt\n+\n+    # Receive result from the hook.\n+    # OK, run this command successfully.\n+    H: PKT-LINE(ok <ref>)\n+    # NO, I reject it.\n+    H: PKT-LINE(ng <ref> <reason>)\n+    # Fall through, let 'receive-pack' to execute it.\n+    H: PKT-LINE(ft <ref>)\n+    # OK, but has an alternate reference.  The alternate reference name\n+    # is given in the third parameter, and other status can be given in\n+    # key-value pairs.\n+    H: PKT-LINE(alt <ref> <alt-ref> old-oid=<old-oid> new-oid=<new-oid>\n+                forced-update)\n+    H: ... ...\n+    H: flush-pkt\n+\n+Each command for the 'proc-receive' hook may point to a pseudo-reference\n+and always has a zero-old as its old-oid, while the 'proc-receive' hook\n+may update an alternate reference and the alternate reference may exist\n+already with a non-zero old-oid.  For this case, this hook may return\n+different OID and different reference name as extended status of the\n+report line.\n+\n+The report of the commands of this hook should have the same order as\n+the input.  The exit status of the 'proc-receive' hook only determines\n+the success or failure of the group of commands sent to it, unless\n+atomic push is in use.\n+\n [[post-receive]]\n post-receive\n ~~~~~~~~~~~~\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395689","messageId":"20200418160334.15631-7-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200414123257.27449-1-worldhello.net@gmail.com","subject":"[PATCH v13 6/8] refs.c: refactor to reuse ref_is_hidden()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-18T16:03:32Z","receivedAt":"2020-04-18T16:04:03Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd new function `ref_is_matched()` to reuse `ref_is_hidden()`. Will use\nthis function for `receive-pack` to check commands with specific\nprefixes.\n\nTest case t5512 covered this change.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n refs.c | 13 +++++++++----\n refs.h |  1 +\n 2 files changed, 10 insertions(+), 4 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 1ab0bb54d3..a20f56a463 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1388,14 +1388,14 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti\n \treturn 0;\n }\n \n-int ref_is_hidden(const char *refname, const char *refname_full)\n+int ref_matches(struct string_list *match_refs, const char *refname, const char *refname_full)\n {\n \tint i;\n \n-\tif (!hide_refs)\n+\tif (!match_refs)\n \t\treturn 0;\n-\tfor (i = hide_refs->nr - 1; i >= 0; i--) {\n-\t\tconst char *match = hide_refs->items[i].string;\n+\tfor (i = match_refs->nr - 1; i >= 0; i--) {\n+\t\tconst char *match = match_refs->items[i].string;\n \t\tconst char *subject;\n \t\tint neg = 0;\n \t\tconst char *p;\n@@ -1421,6 +1421,11 @@ int ref_is_hidden(const char *refname, const char *refname_full)\n \treturn 0;\n }\n \n+int ref_is_hidden(const char *refname, const char *refname_full)\n+{\n+\treturn ref_matches(hide_refs, refname, refname_full);\n+}\n+\n const char *find_descendant_ref(const char *dirname,\n \t\t\t\tconst struct string_list *extras,\n \t\t\t\tconst struct string_list *skip)\ndiff --git a/refs.h b/refs.h\nindex 545029c6d8..d02c72d3ad 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -739,6 +739,7 @@ int parse_hide_refs_config(const char *var, const char *value, const char *);\n  * parameter always points to the full ref name.\n  */\n int ref_is_hidden(const char *, const char *);\n+int ref_matches(struct string_list *, const char *, const char *);\n \n enum ref_type {\n \tREF_TYPE_PER_WORKTREE,\t  /* refs inside refs/ but not shared       */\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395686","messageId":"20200418160334.15631-8-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200414123257.27449-1-worldhello.net@gmail.com","subject":"[PATCH v13 7/8] receive-pack: new config receive.procReceiveRefs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-18T16:03:33Z","receivedAt":"2020-04-18T16:04:04Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd a new multi-valued config variable \"receive.procReceiveRefs\"\nfor `receive-pack` command, like the follows:\n\n    git config --system --add receive.procReceiveRefs refs/for\n    git config --system --add receive.procReceiveRefs refs/drafts\n\nIf the specific prefix strings match the reference names of the commands\nwhich are sent from git client to `receive-pack`, these commands will be\nexecuted by an external hook (named \"proc-receive\"), instead of the\ninternal `execute_commands` function.\n\nFor example, if it is set to \"refs/for\", pushing to a reference such as\n\"refs/for/master\" will not create or update reference \"refs/for/master\",\nbut may create or update a pull request directly by running the hook\n\"proc-receive\".\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/config/receive.txt              | 14 +++\n builtin/receive-pack.c                        | 43 +++++++--\n t/t5411/test-0010-proc-receive-settings.sh    |  7 ++\n t/t5411/test-0040-process-all-refs.sh         | 93 ++++++++++++++++++\n .../test-0041-process-all-refs--porcelain.sh  | 94 +++++++++++++++++++\n 5 files changed, 244 insertions(+), 7 deletions(-)\n create mode 100644 t/t5411/test-0010-proc-receive-settings.sh\n create mode 100644 t/t5411/test-0040-process-all-refs.sh\n create mode 100644 t/t5411/test-0041-process-all-refs--porcelain.sh\n\ndiff --git a/Documentation/config/receive.txt b/Documentation/config/receive.txt\nindex 65f78aac37..e7b967feeb 100644\n--- a/Documentation/config/receive.txt\n+++ b/Documentation/config/receive.txt\n@@ -114,6 +114,20 @@ receive.hideRefs::\n \tAn attempt to update or delete a hidden ref by `git push` is\n \trejected.\n \n+receive.procReceiveRefs::\n+\tThis is a multi-valued variable that defines reference prefixes\n+\tto match the commands in `receive-pack`.  Commands matching the\n+\tprefixes will be executed by an external hook \"proc-receive\",\n+\tinstead of the internal `execute_commands` function.  If this\n+\tvariable is not defined, the \"proc-receive\" hook will never be\n+\tused, and all commands will be executed by the internal\n+\t`execute_commands` function.\n+\n+\tFor example, if this variable is set to \"refs/for\", pushing to\n+\treference such as \"refs/for/master\" will not create or update a\n+\treference named \"refs/for/master\", but may create or update a\n+\tpull request directly by running the hook \"proc-receive\".\n+\n receive.updateServerInfo::\n \tIf set to true, git-receive-pack will run git-update-server-info\n \tafter receiving data from git-push and updating refs.\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 529ac01e1c..25ff718cdf 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -76,6 +76,7 @@ static struct object_id push_cert_oid;\n static struct signature_check sigcheck;\n static const char *push_cert_nonce;\n static const char *cert_nonce_seed;\n+static struct string_list proc_receive_refs;\n \n static const char *NONCE_UNSOLICITED = \"UNSOLICITED\";\n static const char *NONCE_BAD = \"BAD\";\n@@ -228,6 +229,20 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (strcmp(var, \"receive.procreceiverefs\") == 0) {\n+\t\tchar *prefix;\n+\t\tint len;\n+\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tprefix = xstrdup(value);\n+\t\tlen = strlen(prefix);\n+\t\twhile (len && prefix[len - 1] == '/')\n+\t\t\tprefix[--len] = '\\0';\n+\t\tstring_list_append(&proc_receive_refs, prefix);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -1856,15 +1871,26 @@ static void execute_commands(struct command *commands,\n \t * Try to find commands that have special prefix in their reference names,\n \t * and mark them to run an external \"proc-receive\" hook later.\n \t */\n-\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n-\t\t\tcontinue;\n+\tif (proc_receive_refs.nr > 0) {\n+\t\tstruct strbuf refname_full = STRBUF_INIT;\n+\t\tsize_t prefix_len;\n \n-\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n-\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n-\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n-\t\t\trun_proc_receive = 1;\n+\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n+\t\tprefix_len = refname_full.len;\n+\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tif (!should_process_cmd(cmd))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_setlen(&refname_full, prefix_len);\n+\t\t\tstrbuf_addstr(&refname_full, cmd->ref_name);\n+\t\t\tif (ref_matches(&proc_receive_refs, cmd->ref_name, refname_full.buf)) {\n+\t\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\t\trun_proc_receive = 1;\n+\t\t\t}\n \t\t}\n+\n+\t\tstrbuf_release(&refname_full);\n \t}\n \n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n@@ -2329,6 +2355,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n+\tstring_list_init(&proc_receive_refs, 0);\n+\n \tpacket_trace_identity(\"receive-pack\");\n \n \targc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);\n@@ -2444,5 +2472,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \toid_array_clear(&shallow);\n \toid_array_clear(&ref);\n \tfree((void *)push_cert_nonce);\n+\tstring_list_clear(&proc_receive_refs, 0);\n \treturn 0;\n }\ndiff --git a/t/t5411/test-0010-proc-receive-settings.sh b/t/t5411/test-0010-proc-receive-settings.sh\nnew file mode 100644\nindex 0000000000..a36809927b\n--- /dev/null\n+++ b/t/t5411/test-0010-proc-receive-settings.sh\n@@ -0,0 +1,7 @@\n+test_expect_success \"add two receive.procReceiveRefs settings\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit config --add receive.procReceiveRefs refs/for &&\n+\t\tgit config --add receive.procReceiveRefs refs/review/\n+\t)\n+'\ndiff --git a/t/t5411/test-0040-process-all-refs.sh b/t/t5411/test-0040-process-all-refs.sh\nnew file mode 100644\nindex 0000000000..bfb14c53fc\n--- /dev/null\n+++ b/t/t5411/test-0040-process-all-refs.sh\n@@ -0,0 +1,93 @@\n+test_expect_success \"config receive.procReceiveRefs = refs ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config --unset-all receive.procReceiveRefs &&\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup upstream branches ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $B &&\n+\t\tgit update-ref refs/heads/foo $A &&\n+\t\tgit update-ref refs/heads/bar $A &&\n+\t\tgit update-ref refs/heads/baz $A\n+\t)\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ft refs/heads/master\" \\\n+\t\t-r \"ft refs/heads/foo\" \\\n+\t\t-r \"ft refs/heads/bar\" \\\n+\t\t-r \"alt refs/for/master/topic refs/pull/123/head old-oid=$A new-oid=$B \" \\\n+\t\t-r \"alt refs/for/next/topic refs/pull/124/head old-oid=$B new-oid=$A forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"proc-receive: process all refs ($PROTOCOL)\" '\n+\tgit -C workbench push -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> ft refs/heads/master\n+\tremote: proc-receive> ft refs/heads/foo\n+\tremote: proc-receive> ft refs/heads/bar\n+\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n+\tremote: proc-receive> alt refs/for/next/topic refs/pull/124/head old-oid=<COMMIT-B> new-oid=<COMMIT-A> forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> bar\n+\t - [deleted] foo\n+\t + <OID-B>...<OID-A> HEAD -> master (forced update)\n+\t <OID-A>..<OID-B> HEAD -> refs/pull/123/head\n+\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             bar(A)  baz(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+'\ndiff --git a/t/t5411/test-0041-process-all-refs--porcelain.sh b/t/t5411/test-0041-process-all-refs--porcelain.sh\nnew file mode 100644\nindex 0000000000..02b24f4b16\n--- /dev/null\n+++ b/t/t5411/test-0041-process-all-refs--porcelain.sh\n@@ -0,0 +1,94 @@\n+test_expect_success \"config receive.procReceiveRefs = refs ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config --unset-all receive.procReceiveRefs &&\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup upstream branches ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $B &&\n+\t\tgit update-ref refs/heads/foo $A &&\n+\t\tgit update-ref refs/heads/bar $A &&\n+\t\tgit update-ref refs/heads/baz $A\n+\t)\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ft refs/heads/master\" \\\n+\t\t-r \"ft refs/heads/foo\" \\\n+\t\t-r \"ft refs/heads/bar\" \\\n+\t\t-r \"alt refs/for/master/topic refs/pull/123/head old-oid=$A new-oid=$B \" \\\n+\t\t-r \"alt refs/for/next/topic refs/pull/124/head old-oid=$B new-oid=$A forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"proc-receive: process all refs (--porcelain) ($PROTOCOL)\" '\n+\tgit -C workbench push --porcelain -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> ft refs/heads/master\n+\tremote: proc-receive> ft refs/heads/foo\n+\tremote: proc-receive> ft refs/heads/bar\n+\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n+\tremote: proc-receive> alt refs/for/next/topic refs/pull/124/head old-oid=<COMMIT-B> new-oid=<COMMIT-A> forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/bar    <OID-A>..<OID-B>\n+\t-    :refs/heads/foo    [deleted]\n+\t+    HEAD:refs/heads/master    <OID-B>...<OID-A> (forced update)\n+\t     HEAD:refs/pull/123/head    <OID-A>..<OID-B>\n+\t+    HEAD:refs/pull/124/head    <OID-B>...<OID-A> (forced update)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             bar(A)  baz(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+'\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395687","messageId":"20200418160334.15631-6-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200414123257.27449-1-worldhello.net@gmail.com","subject":"[PATCH v13 5/8] receive-pack: feed extended_status to post-receive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-18T16:03:31Z","receivedAt":"2020-04-18T16:04:05Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen commands are fed to the \"post-receive\" hook, `extended_status` will\nbe parsed and the real old-oid, new-oid, reference name will feed to\nthe \"post-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c                        | 57 +++++++++++++++++--\n t/t5411/test-0032-report-alt.sh               | 14 ++---\n t/t5411/test-0033-report-alt--porcelain.sh    | 14 ++---\n .../test-0036-report-multi-alt-for-one-ref.sh | 12 +++-\n ...report-multi-alt-for-one-ref--porcelain.sh | 12 +++-\n t/t5411/test-0038-report-mixed-refs.sh        |  2 +-\n .../test-0039-report-mixed-refs--porcelain.sh |  2 +-\n 7 files changed, 87 insertions(+), 26 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 0081bf1415..529ac01e1c 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -754,17 +754,66 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n {\n \tstruct receive_hook_feed_state *state = state_;\n \tstruct command *cmd = state->cmd;\n+\tstatic char *extended_status = NULL;\n \n \twhile (cmd &&\n \t       state->skip_broken && (cmd->error_string || cmd->did_not_exist))\n \t\tcmd = cmd->next;\n \tif (!cmd)\n \t\treturn -1; /* EOF */\n+\tif (!bufp)\n+\t\treturn 0; /* OK, can feed something. */\n \tstrbuf_reset(&state->buf);\n-\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n-\t\t    oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),\n-\t\t    cmd->ref_name);\n-\tstate->cmd = cmd->next;\n+\tif (cmd->extended_status && extended_status == NULL)\n+\t\textended_status = (char *)cmd->extended_status;\n+\tif (extended_status) {\n+\t\tstruct object_id old_oid;\n+\t\tstruct object_id new_oid;\n+\t\tchar *end;\n+\t\tchar *val;\n+\t\tint len;\n+\n+\t\tend = strstr(extended_status + 4, \"ref=\");\n+\t\tif (end)\n+\t\t\t*(end-1) = '\\0';\n+\n+\t\tval = (char *)parse_feature_value(extended_status, \"old-oid\", &len);\n+\t\tif (val && len && !get_oid_hex(val, &old_oid))\n+\t\t\tstrbuf_addf(&state->buf, \"%s \",\n+\t\t\t\t    oid_to_hex(&old_oid));\n+\t\telse\n+\t\t\tstrbuf_addf(&state->buf, \"%s \",\n+\t\t\t\t    oid_to_hex(&cmd->old_oid));\n+\n+\t\tval = (char *)parse_feature_value(extended_status, \"new-oid\", &len);\n+\t\tif (val && len && !get_oid_hex(val, &new_oid))\n+\t\t\tstrbuf_addf(&state->buf, \"%s \",\n+\t\t\t\t    oid_to_hex(&new_oid));\n+\t\telse\n+\t\t\tstrbuf_addf(&state->buf, \"%s \",\n+\t\t\t\t    oid_to_hex(&cmd->new_oid));\n+\n+\t\tval = (char *)parse_feature_value(extended_status, \"ref\", &len);\n+\t\tif (val && len)\n+\t\t\tstrbuf_add(&state->buf, val, len);\n+\t\telse\n+\t\t\tstrbuf_addstr(&state->buf, cmd->ref_name);\n+\n+\t\tstrbuf_addch(&state->buf, '\\n');\n+\n+\t\tif (end) {\n+\t\t\textended_status = end;\n+\t\t\t*(end-1) = ' ';\n+\t\t} else {\n+\t\t\textended_status = NULL;\n+\t\t\tstate->cmd = cmd->next;\n+\t\t}\n+\t} else {\n+\t\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n+\t\t\t    oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),\n+\t\t\t    cmd->ref_name);\n+\t\tstate->cmd = cmd->next;\n+\t}\n \tif (bufp) {\n \t\t*bufp = state->buf.buf;\n \t\t*sizep = state->buf.len;\ndiff --git a/t/t5411/test-0032-report-alt.sh b/t/t5411/test-0032-report-alt.sh\nindex bf5faf4f2e..25bffd1127 100644\n--- a/t/t5411/test-0032-report-alt.sh\n+++ b/t/t5411/test-0032-report-alt.sh\n@@ -50,7 +50,7 @@ test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref>, $PROTOCOL)\"\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n@@ -80,7 +80,7 @@ test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> forced-update\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n@@ -110,7 +110,7 @@ test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> old-oid=X, $P\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head old-oid=<COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t <OID-B>..<OID-A> HEAD -> refs/pull/123/head\n \tEOF\n@@ -140,7 +140,7 @@ test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X, $PROTOCOL)\"\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t <OID-B>..<OID-A> HEAD -> refs/for/master/topic\n \tEOF\n@@ -170,7 +170,7 @@ test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X new-oid=Y, $P\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \tEOF\n@@ -210,9 +210,9 @@ test_expect_success \"proc-receive: with multiple alt reports ($PROTOCOL)\" '\n \tremote: proc-receive> alt refs/for/next/topic refs/pull/123/head\n \tremote: proc-receive> alt refs/for/master/topic refs/pull/124/head old-oid=<COMMIT-B> forced-update new-oid=<COMMIT-A>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/pull/123/head\n \t * [new reference] HEAD -> refs/for/a/b/c/topic\ndiff --git a/t/t5411/test-0033-report-alt--porcelain.sh b/t/t5411/test-0033-report-alt--porcelain.sh\nindex 3aea911407..9f1b577424 100644\n--- a/t/t5411/test-0033-report-alt--porcelain.sh\n+++ b/t/t5411/test-0033-report-alt--porcelain.sh\n@@ -51,7 +51,7 @@ test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref>, --porcelain,\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n@@ -82,7 +82,7 @@ test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> forced-update\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n@@ -113,7 +113,7 @@ test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> old-oid=X, --\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head old-oid=<COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t     HEAD:refs/pull/123/head    <OID-B>..<OID-A>\n \tDone\n@@ -144,7 +144,7 @@ test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X, --porcelain,\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t     HEAD:refs/for/master/topic    <OID-B>..<OID-A>\n \tDone\n@@ -175,7 +175,7 @@ test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X new-oid=Y, --\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \tDone\n@@ -216,9 +216,9 @@ test_expect_success \"proc-receive: with multiple alt reports (--porcelain, $PROT\n \tremote: proc-receive> alt refs/for/next/topic refs/pull/123/head\n \tremote: proc-receive> alt refs/for/master/topic refs/pull/124/head old-oid=<COMMIT-B> forced-update new-oid=<COMMIT-A>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/pull/123/head    [new reference]\n \t*    HEAD:refs/for/a/b/c/topic    [new reference]\ndiff --git a/t/t5411/test-0036-report-multi-alt-for-one-ref.sh b/t/t5411/test-0036-report-multi-alt-for-one-ref.sh\nindex d82b79ffff..1f6a4e10b2 100644\n--- a/t/t5411/test-0036-report-multi-alt-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-alt-for-one-ref.sh\n@@ -25,7 +25,9 @@ test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 1st r\n \tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 old-oid=<ZERO-OID> new-oid=<COMMIT-A>\n \tremote: proc-receive> alt refs/for/master/topic refs/changes/25/125/1 old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \t * [new reference] HEAD -> refs/changes/24/124/1\n@@ -67,7 +69,9 @@ test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 2nd r\n \tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n \tremote: proc-receive> alt refs/for/master/topic refs/changes/25/125/1 old-oid=<COMMIT-B> new-oid=<COMMIT-A> forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/changes/24/124/1\n \t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n@@ -108,6 +112,7 @@ test_expect_success \"proc-receive: report ok and alt for the same ref ($PROTOCOL\n \tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 new-oid=<COMMIT-B> old-oid=<COMMIT-A>\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/1\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/for/master/topic\n \t <OID-A>..<OID-B> HEAD -> refs/changes/24/124/1\n@@ -146,7 +151,8 @@ test_expect_success \"proc-receive: report multiple response ($PROTOCOL)\" '\n \tremote: proc-receive> alt refs/for/master/topic refs/changes/23/123/1\n \tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/2 old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/changes/23/123/1\n \t <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2\ndiff --git a/t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh\nindex 8cca63c2de..3a08a21b87 100644\n--- a/t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh\n+++ b/t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh\n@@ -25,7 +25,9 @@ test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 1st r\n \tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 old-oid=<ZERO-OID> new-oid=<COMMIT-A>\n \tremote: proc-receive> alt refs/for/master/topic refs/changes/25/125/1 old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \t*    HEAD:refs/changes/24/124/1    [new reference]\n@@ -68,7 +70,9 @@ test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 2nd r\n \tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n \tremote: proc-receive> alt refs/for/master/topic refs/changes/25/125/1 old-oid=<COMMIT-B> new-oid=<COMMIT-A> forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/changes/24/124/1    [new reference]\n \t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n@@ -110,6 +114,7 @@ test_expect_success \"proc-receive: report ok and alt for the same ref (--porcela\n \tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 new-oid=<COMMIT-B> old-oid=<COMMIT-A>\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/1\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/for/master/topic    [new reference]\n \t     HEAD:refs/changes/24/124/1    <OID-A>..<OID-B>\n@@ -149,7 +154,8 @@ test_expect_success \"proc-receive: report multiple response (--porcelain, $PROTO\n \tremote: proc-receive> alt refs/for/master/topic refs/changes/23/123/1\n \tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/2 old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/changes/23/123/1    [new reference]\n \t     HEAD:refs/changes/24/124/2    <OID-A>..<OID-B>\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nindex 566cb6e098..5ac2771914 100644\n--- a/t/t5411/test-0038-report-mixed-refs.sh\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -39,7 +39,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t <OID-A>..<OID-B> <COMMIT-B> -> master\n \t * [new branch] HEAD -> bar\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nindex 4d617abb1a..55b408aca1 100644\n--- a/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -39,7 +39,7 @@ test_expect_success \"proc-receive: report update of mixed refs (--porcelain, $PR\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n \t*    HEAD:refs/heads/bar    [new branch]\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"395691","messageId":"20200418160334.15631-4-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200414123257.27449-1-worldhello.net@gmail.com","subject":"[PATCH v13 3/8] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-18T16:03:29Z","receivedAt":"2020-04-18T16:04:07Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nGit calls an internal `execute_commands` function to handle commands\nsent from client to `git-receive-pack`.  Regardless of what references\nthe user pushes, git creates or updates the corresponding references if\nthe user has write-permission.  A contributor who has no\nwrite-permission, cannot push to the repository directly.  So, the\ncontributor has to write commits to an alternate location, and sends\npull request by emails or by other ways.  We call this workflow as a\ndistributed workflow.\n\nIt would be more convenient to work in a centralized workflow like what\nGerrit provided for some cases.  For example, a read-only user who\ncannot push to a branch directly can run the following `git push`\ncommand to push commits to a pseudo reference (has a prefix \"refs/for/\",\nnot \"refs/heads/\") to create a code review.\n\n    git push origin \\\n        HEAD:refs/for/<branch-name>/<session>\n\nThe `<branch-name>` in the above example can be as simple as \"master\",\nor a more complicated branch name like \"foo/bar\".  The `<session>` in\nthe above example command can be the local branch name of the client\nside, such as \"my/topic\".\n\nWe cannot implement a centralized workflow elegantly by using\n\"pre-receive\" + \"post-receive\", because Git will call the internal\nfunction \"execute_commands\" to create references (even the special\npseudo reference) between these two hooks.  Even though we can delete\nthe temporarily created pseudo reference via the \"post-receive\" hook,\nhaving a temporary reference is not safe for concurrent pushes.\n\nSo, add a filter and a new handler to support this kind of workflow.\nThe filter will check the prefix of the reference name, and if the\ncommand has a special reference name, the filter will turn a specific\nfield (`run_proc_receive`) on for the command.  Commands with this filed\nturned on will be executed by a new handler (an hook named\n\"proc-receive\") instead of the internal `execute_commands` function.\nWe can use this \"proc-receive\" command to create pull requests or send\nemails for code review.\n\nSuggested by Junio, this \"proc-receive\" hook reads the commands,\npush-options (optional), and send result using a protocol in pkt-line\nformat.  In the following example, The letter \"S\" stands for\n\"receive-pack\" and letter \"H\" stands for the hook.\n\n    # Version and features negotiation.\n    S: PKT-LINE(version=1\\0push-options atomic...)\n    S: flush-pkt\n    H: PKT-LINE(version=1\\0push-options...)\n    H: flush-pkt\n\n    # Send commands from server to the hook.\n    S: PKT-LINE(<old-oid> <new-oid> <ref>)\n    S: ... ...\n    S: flush-pkt\n    # Send push-options only if the 'push-options' feature is enabled.\n    S: PKT-LINE(push-option)\n    S: ... ...\n    S: flush-pkt\n\n    # Receive result from the hook.\n    # OK, run this command successfully.\n    H: PKT-LINE(ok <ref>)\n    # NO, I reject it.\n    H: PKT-LINE(ng <ref> <reason>)\n    # Fall through, let 'receive-pack' to execute it.\n    H: PKT-LINE(ft <ref>)\n    # OK, but has an alternate reference.  The alternate reference name\n    # is given in the third parameter, and other status can be given in\n    # key-value pairs.\n    H: PKT-LINE(alt <ref> <alt-ref> old-oid=<old-oid> new-oid=<new-oid>\n                forced-update)\n    H: ... ...\n    H: flush-pkt\n\nAfter receiving a command, the hook will execute the command, and may\ncreate/update different reference.  For example, a command for a pseudo\nreference \"refs/for/master/topic\" may create/update different reference\nsuch as \"refs/pull/123/head\".  The alternate reference name and other\nstatus are given in key-value pairs as extended status of the report\nline.\n\nThe list of commands returned from \"proc-receive\" will replace the\nrelevant commands that are sent from user to \"receive-pack\", and\n\"receive-pack\" will continue to run the \"execute_commands\" function and\nother routines.  Finally, the result of the execution of these commands\nwill be reported to end user.\n\nThe reporting function from \"receive-pack\" to \"send-pack\" is extended\nusing a backward compatible way by adding key-value pairs after an null\ncharacter, like:\n\n    # OK, run this command successfully with optional extended-status.\n    ok <reference>\\0ref=refs/pull/123/head old-oid=...\n\n    # NO, I reject it.\n    ng <reference> <error message>\n\nParsing the extended status of the report lines for \"send-pack\" is not\nimplemented in this commit, so we can make a compatible test on old\nversion of a Git client.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Makefile                                      |   1 +\n builtin/receive-pack.c                        | 356 +++++++++++++++++-\n t/helper/test-proc-receive.c                  | 175 +++++++++\n t/helper/test-tool.c                          |   1 +\n t/helper/test-tool.h                          |   1 +\n t/t5411/test-0011-no-hook-error.sh            |  64 ++++\n t/t5411/test-0012-no-hook-error--porcelain.sh |  66 ++++\n t/t5411/test-0013-bad-protocol.sh             | 225 +++++++++++\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 160 ++++++++\n t/t5411/test-0020-report-ng.sh                |  67 ++++\n t/t5411/test-0021-report-ng--porcelain.sh     |  69 ++++\n t/t5411/test-0022-report-unexpect-ref.sh      |  45 +++\n ...est-0023-report-unexpect-ref--porcelain.sh |  46 +++\n t/t5411/test-0024-report-unknown-ref.sh       |  34 ++\n ...test-0025-report-unknown-ref--porcelain.sh |  35 ++\n t/t5411/test-0026-push-options.sh             |  79 ++++\n t/t5411/test-0027-push-options--porcelain.sh  |  82 ++++\n t/t5411/test-0030-report-ok.sh                |  35 ++\n t/t5411/test-0031-report-ok--porcelain.sh     |  36 ++\n t/t5411/test-0032-report-alt.sh               | 229 +++++++++++\n t/t5411/test-0033-report-alt--porcelain.sh    | 236 ++++++++++++\n t/t5411/test-0034-report-ft.sh                |  42 +++\n t/t5411/test-0035-report-ft--porcelain.sh     |  43 +++\n .../test-0036-report-multi-alt-for-one-ref.sh | 155 ++++++++\n ...report-multi-alt-for-one-ref--porcelain.sh | 159 ++++++++\n t/t5411/test-0038-report-mixed-refs.sh        |  74 ++++\n .../test-0039-report-mixed-refs--porcelain.sh |  75 ++++\n 27 files changed, 2581 insertions(+), 9 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100644 t/t5411/test-0011-no-hook-error.sh\n create mode 100644 t/t5411/test-0012-no-hook-error--porcelain.sh\n create mode 100644 t/t5411/test-0013-bad-protocol.sh\n create mode 100644 t/t5411/test-0014-bad-protocol--porcelain.sh\n create mode 100644 t/t5411/test-0020-report-ng.sh\n create mode 100644 t/t5411/test-0021-report-ng--porcelain.sh\n create mode 100644 t/t5411/test-0022-report-unexpect-ref.sh\n create mode 100644 t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n create mode 100644 t/t5411/test-0024-report-unknown-ref.sh\n create mode 100644 t/t5411/test-0025-report-unknown-ref--porcelain.sh\n create mode 100644 t/t5411/test-0026-push-options.sh\n create mode 100644 t/t5411/test-0027-push-options--porcelain.sh\n create mode 100644 t/t5411/test-0030-report-ok.sh\n create mode 100644 t/t5411/test-0031-report-ok--porcelain.sh\n create mode 100644 t/t5411/test-0032-report-alt.sh\n create mode 100644 t/t5411/test-0033-report-alt--porcelain.sh\n create mode 100644 t/t5411/test-0034-report-ft.sh\n create mode 100644 t/t5411/test-0035-report-ft--porcelain.sh\n create mode 100644 t/t5411/test-0036-report-multi-alt-for-one-ref.sh\n create mode 100644 t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh\n create mode 100644 t/t5411/test-0038-report-mixed-refs.sh\n create mode 100644 t/t5411/test-0039-report-mixed-refs--porcelain.sh\n\ndiff --git a/Makefile b/Makefile\nindex ef1ff2228f..1c52c280ce 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -725,6 +725,7 @@ TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\n+TEST_BUILTINS_OBJS += test-proc-receive.o\n TEST_BUILTINS_OBJS += test-progress.o\n TEST_BUILTINS_OBJS += test-reach.o\n TEST_BUILTINS_OBJS += test-read-cache.o\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 2cc18bbffd..0081bf1415 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -308,11 +308,15 @@ static void write_head_info(void)\n \tpacket_flush(1);\n }\n \n+#define RUN_PROC_RECEIVE_SCHEDULED\t1\n+#define RUN_PROC_RECEIVE_RETURNED\t2\n struct command {\n \tstruct command *next;\n \tconst char *error_string;\n+\tconst char *extended_status;\n \tunsigned int skip_update:1,\n-\t\t     did_not_exist:1;\n+\t\t     did_not_exist:1,\n+\t\t     run_proc_receive:2;\n \tint index;\n \tstruct object_id old_oid;\n \tstruct object_id new_oid;\n@@ -817,6 +821,307 @@ static int run_update_hook(struct command *cmd)\n \treturn finish_command(&proc);\n }\n \n+static struct command *find_command_by_refname(const struct command *list,\n+\t\t\t\t\t       const char *refname)\n+{\n+\tfor (; list; list = list->next)\n+\t\tif (!strcmp(list->ref_name, refname))\n+\t\t\treturn (struct command *)list;\n+\treturn NULL;\n+}\n+\n+static int read_proc_receive_report(struct packet_reader *reader,\n+\t\t\t\t    struct command *commands,\n+\t\t\t\t    struct strbuf *errmsg)\n+{\n+\tstruct command *cmd;\n+\tstruct command *hint = NULL;\n+\tstruct strbuf extended_status = STRBUF_INIT;\n+\tint code = 0;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *status;\n+\t\tchar *refname;\n+\t\tchar *remains = NULL;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\t\tstatus = reader->line;\n+\t\trefname = strchr(status, ' ');\n+\t\tif (refname) {\n+\t\t\t*refname++ = '\\0';\n+\t\t\tremains = strchr(refname, ' ');\n+\t\t\tif (remains)\n+\t\t\t\t*remains++ = '\\0';\n+\t\t}\n+\t\tif (!refname || strlen(refname) == 0) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive expected '<status> <ref> ...', got '%s'\\n\",\n+\t\t\t\t    reader->line);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/* first try searching at our hint, falling back to all refs */\n+\t\tif (hint)\n+\t\t\thint = find_command_by_refname(hint, refname);\n+\t\tif (!hint)\n+\t\t\thint = find_command_by_refname(commands, refname);\n+\t\tif (!hint) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unknown ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!hint->run_proc_receive) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unexpected ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(status, \"ng\")) {\n+\t\t\tif (remains)\n+\t\t\t\thint->error_string = xstrdup(remains);\n+\t\t\telse\n+\t\t\t\thint->error_string = \"failed\";\n+\t\t\tcode = -1;\n+\t\t} else if (!strcmp(\"ft\", status)) {\n+\t\t\t/* Reset \"run_proc_receive\" field, and continue to run in \"receive-pack\" */\n+\t\t\thint->run_proc_receive = 0;\n+\t\t} else if (!strcmp(\"alt\", status)) {\n+\t\t\tchar *alt_ref = NULL;\n+\t\t\tchar *p1, *p2;\n+\n+\t\t\tif (!remains) {\n+\t\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'alt <ref> <alt-ref> ...', got 'alt %s'\\n\",\n+\t\t\t\t\t    refname);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tp1 = strchr(remains, '=');\n+\t\t\tp2 = strchr(remains, ' ');\n+\t\t\tif (!p1) {\n+\t\t\t\t/*\n+\t\t\t\t * \"alt <ref> <alt-ref>\", or\n+\t\t\t\t * \"alt <ref> <alt-ref> forced-update\"\n+\t\t\t\t */\n+\t\t\t\talt_ref = remains;\n+\t\t\t\tremains = p2;\n+\t\t\t\tif (remains)\n+\t\t\t\t\t*remains++ = '\\0';\n+\t\t\t} else if (!p2) {\n+\t\t\t\t/* \"alt <ref> old-oid=<oid>\" */\n+\t\t\t\talt_ref = NULL;\n+\t\t\t} else if (p1 > p2) {\n+\t\t\t\t/* \"alt <ref> <alt-ref> old-oid=<oid>\" */\n+\t\t\t\talt_ref = remains;\n+\t\t\t\tremains = p2;\n+\t\t\t\tif (remains)\n+\t\t\t\t\t*remains++ = '\\0';\n+\t\t\t} else {\n+\t\t\t\t/* \"alt <ref> old-oid=<oid1> new-oid=<oid2>\" */\n+\t\t\t\talt_ref = NULL;\n+\t\t\t}\n+\n+\t\t\tif (hint->extended_status)\n+\t\t\t\tstrbuf_addstr(&extended_status, hint->extended_status);\n+\n+\t\t\t/*\n+\t\t\t * Already received an \"ok <ref>\" response, add an\n+\t\t\t * additional \"ref=<ref>\" field.\n+\t\t\t */\n+\t\t\tif (extended_status.len == 0 &&\n+\t\t\t    hint->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)\n+\t\t\t\tstrbuf_addf(&extended_status, \"ref=%s\", refname);\n+\n+\t\t\tif (alt_ref)\n+\t\t\t\tstrbuf_addf(&extended_status, \"%sref=%s\",\n+\t\t\t\t\t    extended_status.len > 0 ? \" \": \"\",\n+\t\t\t\t\t    alt_ref);\n+\t\t\telse if (extended_status.len > 0)\n+\t\t\t\t/*\n+\t\t\t\t * Start new extended status for refname, if it\n+\t\t\t\t * is not the first report for this reference.\n+\t\t\t\t */\n+\t\t\t\tstrbuf_addf(&extended_status, \" ref=%s\", refname);\n+\n+\t\t\tif (remains) {\n+\t\t\t\tconst char *val;\n+\t\t\t\tint len;\n+\n+\t\t\t\tval = parse_feature_value(remains, \"old-oid\", &len);\n+\t\t\t\tif (val && len > 0)\n+\t\t\t\t\tif (!parse_oid_hex(val, &old_oid, &val))\n+\t\t\t\t\t\tstrbuf_addf(&extended_status, \"%sold-oid=%s\",\n+\t\t\t\t\t\t\t   extended_status.len > 0 ? \" \": \"\",\n+\t\t\t\t\t\t\t   oid_to_hex(&old_oid));\n+\n+\t\t\t\tval = parse_feature_value(remains, \"new-oid\", &len);\n+\t\t\t\tif (val && len > 0)\n+\t\t\t\t\tif (!parse_oid_hex(val, &new_oid, &val))\n+\t\t\t\t\t\tstrbuf_addf(&extended_status, \"%snew-oid=%s\",\n+\t\t\t\t\t\t\t   extended_status.len > 0 ? \" \": \"\",\n+\t\t\t\t\t\t\t   oid_to_hex(&new_oid));\n+\n+\t\t\t\tif (parse_feature_request(remains, \"forced-update\"))\n+\t\t\t\t\tstrbuf_addf(&extended_status, \"%sforced-update\",\n+\t\t\t\t\t\t    extended_status.len > 0 ? \" \": \"\");\n+\t\t\t}\n+\n+\t\t\tif (extended_status.len > 0) {\n+\t\t\t\tfree((void *)hint->extended_status);\n+\t\t\t\thint->extended_status = strbuf_detach(&extended_status, NULL);\n+\t\t\t}\n+\t\t} else if (strcmp(\"ok\", status)) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported bad status '%s' on ref '%s'\\n\",\n+\t\t\t\t    status, refname);\n+\t\t\tcode = -1;\n+\t\t\t/* Skip marking it as RUN_PROC_RECEIVE_RETURNED */\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (hint->run_proc_receive)\n+\t\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n+\t}\n+\n+\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\tif (cmd->run_proc_receive && !cmd->error_string &&\n+\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {\n+\t\t    cmd->error_string = \"proc-receive failed to report status\";\n+\t\t    code = -1;\n+\t\t}\n+\n+\treturn code;\n+}\n+\n+static int run_proc_receive_hook(struct command *commands,\n+\t\t\t\t const struct string_list *push_options)\n+{\n+\tstruct child_process proc = CHILD_PROCESS_INIT;\n+\tstruct async muxer;\n+\tstruct command *cmd;\n+\tconst char *argv[2];\n+\tstruct packet_reader reader;\n+\tstruct strbuf cap = STRBUF_INIT;\n+\tstruct strbuf errmsg = STRBUF_INIT;\n+\tint pr_use_push_options = 0;\n+\tint version = 0;\n+\tint code;\n+\n+\targv[0] = find_hook(\"proc-receive\");\n+\tif (!argv[0]) {\n+\t\trp_error(\"cannot find hook 'proc-receive'\");\n+\t\treturn -1;\n+\t}\n+\targv[1] = NULL;\n+\n+\tproc.argv = argv;\n+\tproc.in = -1;\n+\tproc.out = -1;\n+\tproc.trace2_hook_name = \"proc-receive\";\n+\n+\tif (use_sideband) {\n+\t\tmemset(&muxer, 0, sizeof(muxer));\n+\t\tmuxer.proc = copy_to_sideband;\n+\t\tmuxer.in = -1;\n+\t\tcode = start_async(&muxer);\n+\t\tif (code)\n+\t\t\treturn code;\n+\t\tproc.err = muxer.in;\n+\t} else {\n+\t\tproc.err = 0;\n+\t}\n+\n+\tcode = start_command(&proc);\n+\tif (code) {\n+\t\tif (use_sideband)\n+\t\t\tfinish_async(&muxer);\n+\t\treturn code;\n+\t}\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\n+\t/* Version negotiaton */\n+\tpacket_reader_init(&reader, proc.out, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_GENTLE_ON_EOF);\n+\tif (use_atomic)\n+\t\tstrbuf_addstr(&cap, \" atomic\");\n+\tif (use_push_options)\n+\t\tstrbuf_addstr(&cap, \" push-options\");\n+\tif (cap.len) {\n+\t\tpacket_write_fmt(proc.in, \"version=1%c%s\\n\", '\\0', cap.buf + 1);\n+\t\tstrbuf_release(&cap);\n+\t} else {\n+\t\tpacket_write_fmt(proc.in, \"version=1\\n\");\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader.pktlen > 8 && starts_with(reader.line, \"version=\")) {\n+\t\t\tversion = atoi(reader.line + 8);\n+\t\t\tlinelen = strlen(reader.line);\n+\t\t\tif (linelen < reader.pktlen) {\n+\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tpr_use_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (version != 1) {\n+\t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n+\t\t\t    version);\n+\t\tcode = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\t/* Send commands */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)\n+\t\t\tcontinue;\n+\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n+\t\t\t\t oid_to_hex(&cmd->old_oid),\n+\t\t\t\t oid_to_hex(&cmd->new_oid),\n+\t\t\t\t cmd->ref_name);\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\t/* Send push options */\n+\tif (pr_use_push_options) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tfor_each_string_list_item(item, push_options)\n+\t\t\tpacket_write_fmt(proc.in, \"%s\", item->string);\n+\t\tpacket_flush(proc.in);\n+\t}\n+\n+\t/* Read result from proc-receive */\n+\tcode = read_proc_receive_report(&reader, commands, &errmsg);\n+\n+cleanup:\n+\tclose(proc.in);\n+\tclose(proc.out);\n+\tif (use_sideband)\n+\t\tfinish_async(&muxer);\n+\tif (finish_command(&proc))\n+\t\tcode = -1;\n+\tif (errmsg.len >0) {\n+\t\tchar *p = errmsg.buf;\n+\n+\t\tp += errmsg.len - 1;\n+\t\tif (*p == '\\n')\n+\t\t\t*p = '\\0';\n+\t\trp_error(\"%s\", errmsg.buf);\n+\t\tstrbuf_release(&errmsg);\n+\t}\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn code;\n+}\n+\n static char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n@@ -1392,7 +1697,7 @@ static void execute_commands_non_atomic(struct command *commands,\n \tstruct strbuf err = STRBUF_INIT;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\ttransaction = ref_transaction_begin(&err);\n@@ -1432,7 +1737,7 @@ static void execute_commands_atomic(struct command *commands,\n \t}\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\tcmd->error_string = update(cmd, si);\n@@ -1468,6 +1773,7 @@ static void execute_commands(struct command *commands,\n \tstruct iterate_data data;\n \tstruct async muxer;\n \tint err_fd = 0;\n+\tint run_proc_receive = 0;\n \n \tif (unpacker_error) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n@@ -1497,6 +1803,21 @@ static void execute_commands(struct command *commands,\n \n \treject_updates_to_hidden(commands);\n \n+\t/*\n+\t * Try to find commands that have special prefix in their reference names,\n+\t * and mark them to run an external \"proc-receive\" hook later.\n+\t */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!should_process_cmd(cmd))\n+\t\t\tcontinue;\n+\n+\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n+\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n+\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\trun_proc_receive = 1;\n+\t\t}\n+\t}\n+\n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n \t\t\tif (!cmd->error_string)\n@@ -1523,6 +1844,12 @@ static void execute_commands(struct command *commands,\n \tfree(head_name_to_free);\n \thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, NULL, NULL);\n \n+\tif (run_proc_receive &&\n+\t    run_proc_receive_hook(commands, push_options))\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tif (!cmd->error_string  && (cmd->run_proc_receive || use_atomic))\n+\t\t\t\tcmd->error_string = \"fail to run proc-receive hook\";\n+\n \tif (use_atomic)\n \t\texecute_commands_atomic(commands, si);\n \telse\n@@ -1899,12 +2226,23 @@ static void report(struct command *commands, const char *unpack_status)\n \tpacket_buf_write(&buf, \"unpack %s\\n\",\n \t\t\t unpack_status ? unpack_status : \"ok\");\n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!cmd->error_string)\n-\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n-\t\t\t\t\t cmd->ref_name);\n-\t\telse\n-\t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n-\t\t\t\t\t cmd->ref_name, cmd->error_string);\n+\t\tif (!cmd->error_string) {\n+\t\t\tif (cmd->extended_status)\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s%c%s\\n\",\n+\t\t\t\t\t\t cmd->ref_name, '\\0',\n+\t\t\t\t\t\t cmd->extended_status);\n+\t\t\telse\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n+\t\t\t\t\t\t cmd->ref_name);\n+\t\t} else {\n+\t\t\tif (cmd->extended_status)\n+\t\t\t\tpacket_buf_write(&buf, \"ng %s %s%c%s\\n\",\n+\t\t\t\t\t\t cmd->ref_name, cmd->error_string,\n+\t\t\t\t\t\t '\\0', cmd->extended_status);\n+\t\t\telse\n+\t\t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n+\t\t\t\t\t\t cmd->ref_name, cmd->error_string);\n+\t\t}\n \t}\n \tpacket_buf_flush(&buf);\n \ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nnew file mode 100644\nindex 0000000000..6ace1863b0\n--- /dev/null\n+++ b/t/helper/test-proc-receive.c\n@@ -0,0 +1,175 @@\n+#include \"cache.h\"\n+#include \"connect.h\"\n+#include \"parse-options.h\"\n+#include \"pkt-line.h\"\n+#include \"sigchain.h\"\n+#include \"string-list.h\"\n+#include \"test-tool.h\"\n+\n+static const char *proc_receive_usage[] = {\n+\t\"test-tool proc-receive [<options>...]\",\n+\tNULL\n+};\n+\n+static int die_version = 0;\n+static int die_readline = 0;\n+static int no_push_options = 0;\n+static int use_atomic = 0;\n+static int use_push_options = 0;\n+static int verbose = 0;\n+static int version = 1;\n+static struct string_list returns = STRING_LIST_INIT_NODUP;\n+\n+struct command {\n+\tstruct command *next;\n+\tconst char *error_string;\n+\tunsigned int skip_update:1,\n+\t\t     did_not_exist:1;\n+\tint index;\n+\tstruct object_id old_oid;\n+\tstruct object_id new_oid;\n+\tchar ref_name[FLEX_ARRAY]; /* more */\n+};\n+\n+static void proc_receive_verison(struct packet_reader *reader) {\n+\tint server_version = 0;\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader->pktlen > 8 && starts_with(reader->line, \"version=\")) {\n+\t\t\tserver_version = atoi(reader->line+8);\n+\t\t\tlinelen = strlen(reader->line);\n+\t\t\tif (linelen < reader->pktlen) {\n+\t\t\t\tconst char *feature_list = reader->line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"atomic\"))\n+\t\t\t\t\tuse_atomic= 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tuse_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (server_version != 1 || die_version)\n+\t\tdie(\"bad protocol version: %d\", server_version);\n+\n+\tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n+\t\t\t version, '\\0',\n+\t\t\t use_push_options && !no_push_options ? \"push-options\": \"\");\n+\tpacket_flush(1);\n+}\n+\n+static void proc_receive_read_commands(struct packet_reader *reader,\n+\t\t\t\t       struct command **commands)\n+{\n+\tstruct command **tail = commands;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tstruct command *cmd;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    die_readline)\n+\t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n+\t\t\t    reader->line);\n+\t\trefname = p;\n+\t\tFLEX_ALLOC_STR(cmd, ref_name, refname);\n+\t\toidcpy(&cmd->old_oid, &old_oid);\n+\t\toidcpy(&cmd->new_oid, &new_oid);\n+\n+\t\t*tail = cmd;\n+\t\ttail = &cmd->next;\n+\t}\n+}\n+\n+static void proc_receive_read_push_options(struct packet_reader *reader,\n+\t\t\t\t\t   struct string_list *options)\n+{\n+\n+\tif (no_push_options || !use_push_options)\n+\t       return;\n+\n+\twhile (1) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tstring_list_append(options, reader->line);\n+\t}\n+}\n+\n+int cmd__proc_receive(int argc, const char **argv)\n+{\n+\tstruct packet_reader reader;\n+\tstruct command *commands = NULL;\n+\tstruct string_list push_options = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"no-push-options\", &no_push_options,\n+\t\t\t \"disable push options\"),\n+\t\tOPT_BOOL(0, \"die-version\", &die_version,\n+\t\t\t \"die during version negotiation\"),\n+\t\tOPT_BOOL(0, \"die-readline\", &die_readline,\n+\t\t\t \"die when readline\"),\n+\t\tOPT_STRING_LIST('r', \"return\", &returns, \"old/new/ref/status/msg\",\n+\t\t\t\t\"return of results\"),\n+\t\tOPT__VERBOSE(&verbose, \"be verbose\"),\n+\t\tOPT_INTEGER('V', \"version\", &version,\n+\t\t\t    \"use this protocol version number\"),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, \"test-tools\", options, proc_receive_usage, 0);\n+\tif (argc > 0)\n+\t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n+\n+\tpacket_reader_init(&reader, 0, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\tproc_receive_verison(&reader);\n+\tproc_receive_read_commands(&reader, &commands);\n+\tproc_receive_read_push_options(&reader, &push_options);\n+\n+\tif (verbose) {\n+\t\tstruct command *cmd;\n+\n+\t\tif (use_push_options || use_atomic)\n+\t\t\tfprintf(stderr, \"proc-receive:%s%s\\n\",\n+\t\t\t\tuse_atomic? \" atomic\": \"\",\n+\t\t\t\tuse_push_options ? \" push_options\": \"\");\n+\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tfprintf(stderr, \"proc-receive< %s %s %s\\n\",\n+\t\t\t\toid_to_hex(&cmd->old_oid),\n+\t\t\t\toid_to_hex(&cmd->new_oid),\n+\t\t\t\tcmd->ref_name);\n+\n+\t\tif (push_options.nr > 0)\n+\t\t\tfor_each_string_list_item(item, &push_options)\n+\t\t\t\tfprintf(stderr, \"proc-receive< %s\\n\", item->string);\n+\n+\t\tif (returns.nr)\n+\t\t\tfor_each_string_list_item(item, &returns)\n+\t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n+\t}\n+\n+\tif (returns.nr)\n+\t\tfor_each_string_list_item(item, &returns)\n+\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n+\tpacket_flush(1);\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 31eedcd241..f865433a61 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -44,6 +44,7 @@ static struct test_cmd cmds[] = {\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\n+\t{ \"proc-receive\", cmd__proc_receive},\n \t{ \"progress\", cmd__progress },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 4eb5e6609e..059008cf22 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -34,6 +34,7 @@ int cmd__parse_pathspec_file(int argc, const char** argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\n+int cmd__proc_receive(int argc, const char **argv);\n int cmd__progress(int argc, const char **argv);\n int cmd__reach(int argc, const char **argv);\n int cmd__read_cache(int argc, const char **argv);\ndiff --git a/t/t5411/test-0011-no-hook-error.sh b/t/t5411/test-0011-no-hook-error.sh\nnew file mode 100644\nindex 0000000000..bb6ec92a92\n--- /dev/null\n+++ b/t/t5411/test-0011-no-hook-error.sh\n@@ -0,0 +1,64 @@\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\t$B:master \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] <COMMIT-B> -> master (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0012-no-hook-error--porcelain.sh b/t/t5411/test-0012-no-hook-error--porcelain.sh\nnew file mode 100644\nindex 0000000000..e3bd0962ab\n--- /dev/null\n+++ b/t/t5411/test-0012-no-hook-error--porcelain.sh\n@@ -0,0 +1,66 @@\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, fail to push special ref (--porcelain, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, all failed for atomic push (--porcelain, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --porcelain --atomic origin \\\n+\t\t$B:master \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t!    <COMMIT-B>:refs/heads/master    [remote rejected] (fail to run proc-receive hook)\n+\t!    HEAD:refs/heads/next    [remote rejected] (fail to run proc-receive hook)\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh\nnew file mode 100644\nindex 0000000000..a375ddffd9\n--- /dev/null\n+++ b/t/t5411/test-0013-bad-protocol.sh\n@@ -0,0 +1,225 @@\n+test_expect_success \"setup proc-receive hook (unknown version, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\t# Check status report for git-push\n+\tsed -n \\\n+\t\t-e \"/^To / { p; n; p; }\" \\\n+\t\t<actual >actual-report &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual-report &&\n+\n+\t# Check error message from \"receive-pack\", but ignore unstable fatal error\n+\t# message (\"remote: fatal: the remote end hung up unexpectedly\") which\n+\t# is different from the remote HTTP server with different locale settings.\n+\tgrep \"^remote: error:\" <actual >actual-error &&\n+\tcat >expect <<-EOF &&\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tEOF\n+\ttest_cmp expect actual-error &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-version, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-version\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-version, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: fatal: bad protocol version: 1\n+\tremote: error: proc-receive version \"0\" is not supported\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-readline, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-readline\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-readline, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: fatal: protocol error: expected \"old new ref\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok\n+\tremote: error: proc-receive expected \"<status> <ref> ...\", got \"ok\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"xx refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> xx refs/for/master/topic\n+\tremote: error: proc-receive reported bad status \"xx\" on ref \"refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0014-bad-protocol--porcelain.sh b/t/t5411/test-0014-bad-protocol--porcelain.sh\nnew file mode 100644\nindex 0000000000..96bbed08fe\n--- /dev/null\n+++ b/t/t5411/test-0014-bad-protocol--porcelain.sh\n@@ -0,0 +1,160 @@\n+test_expect_success \"setup proc-receive hook (unknown version, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (unknown version, --porcelain, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\t# Check status report for git-push\n+\tsed -n \\\n+\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n+\t\t<actual >actual-report &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual-report &&\n+\n+\t# Check error message from \"receive-pack\", but ignore unstable fatal error\n+\t# message (\"remote: fatal: the remote end hung up unexpectedly\") which\n+\t# is different from the remote HTTP server with different locale settings.\n+\tgrep \"^remote: error:\" <actual >actual-error &&\n+\tcat >expect <<-EOF &&\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tEOF\n+\ttest_cmp expect actual-error &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (no report, --porcelain, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (no ref, --porcelain, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok\n+\tremote: error: proc-receive expected \"<status> <ref> ...\", got \"ok\"\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"xx refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (unknown status, --porcelain, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> xx refs/for/master/topic\n+\tremote: error: proc-receive reported bad status \"xx\" on ref \"refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0020-report-ng.sh b/t/t5411/test-0020-report-ng.sh\nnew file mode 100644\nindex 0000000000..f726b7ca9c\n--- /dev/null\n+++ b/t/t5411/test-0020-report-ng.sh\n@@ -0,0 +1,67 @@\n+test_expect_success \"setup proc-receive hook (ng, no message, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic error msg\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0021-report-ng--porcelain.sh b/t/t5411/test-0021-report-ng--porcelain.sh\nnew file mode 100644\nindex 0000000000..5c90d63287\n--- /dev/null\n+++ b/t/t5411/test-0021-report-ng--porcelain.sh\n@@ -0,0 +1,69 @@\n+test_expect_success \"setup proc-receive hook (ng, no message, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, no message, --porcelain, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (failed)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, with message, --porcelain, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic error msg\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (error msg)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0022-report-unexpect-ref.sh b/t/t5411/test-0022-report-unexpect-ref.sh\nnew file mode 100644\nindex 0000000000..92a415b929\n--- /dev/null\n+++ b/t/t5411/test-0022-report-unexpect-ref.sh\n@@ -0,0 +1,45 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: error: proc-receive reported status on unexpected ref: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\ndiff --git a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\nnew file mode 100644\nindex 0000000000..f10715c2dc\n--- /dev/null\n+++ b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n@@ -0,0 +1,46 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: report unexpected ref (--porcelain, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: error: proc-receive reported status on unexpected ref: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\ndiff --git a/t/t5411/test-0024-report-unknown-ref.sh b/t/t5411/test-0024-report-unknown-ref.sh\nnew file mode 100644\nindex 0000000000..c3946f329a\n--- /dev/null\n+++ b/t/t5411/test-0024-report-unknown-ref.sh\n@@ -0,0 +1,34 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: report unknown reference ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0025-report-unknown-ref--porcelain.sh b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\nnew file mode 100644\nindex 0000000000..b5a5d9b992\n--- /dev/null\n+++ b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\n@@ -0,0 +1,35 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: report unknown reference (--porcelain, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/a/b/c/my/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh\nnew file mode 100644\nindex 0000000000..d0c4da8b23\n--- /dev/null\n+++ b/t/t5411/test-0026-push-options.sh\n@@ -0,0 +1,79 @@\n+test_expect_success \"setup proc-receive hook and disable push-options ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions false &&\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"proc-receive: not support push options ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n+\t\tactual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"proc-receive: push with options ($PROTOCOL)\" '\n+\tgit -C workbench push \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\ndiff --git a/t/t5411/test-0027-push-options--porcelain.sh b/t/t5411/test-0027-push-options--porcelain.sh\nnew file mode 100644\nindex 0000000000..243580db93\n--- /dev/null\n+++ b/t/t5411/test-0027-push-options--porcelain.sh\n@@ -0,0 +1,82 @@\n+test_expect_success \"setup proc-receive hook and disable push-options ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions false &&\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"proc-receive: not support push options (--porcelain, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t--porcelain \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n+\t\tactual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"proc-receive: push with options (--porcelain, $PROTOCOL)\" '\n+\tgit -C workbench push \\\n+\t\t--porcelain \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\ndiff --git a/t/t5411/test-0030-report-ok.sh b/t/t5411/test-0030-report-ok.sh\nnew file mode 100644\nindex 0000000000..44c99d3831\n--- /dev/null\n+++ b/t/t5411/test-0030-report-ok.sh\n@@ -0,0 +1,35 @@\n+test_expect_success \"setup proc-receive hook (ok, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0031-report-ok--porcelain.sh b/t/t5411/test-0031-report-ok--porcelain.sh\nnew file mode 100644\nindex 0000000000..2a10bdc749\n--- /dev/null\n+++ b/t/t5411/test-0031-report-ok--porcelain.sh\n@@ -0,0 +1,36 @@\n+test_expect_success \"setup proc-receive hook (ok, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok (--porcelain, $PROTOCOL)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0032-report-alt.sh b/t/t5411/test-0032-report-alt.sh\nnew file mode 100644\nindex 0000000000..dec4afc4c1\n--- /dev/null\n+++ b/t/t5411/test-0032-report-alt.sh\n@@ -0,0 +1,229 @@\n+test_expect_success \"setup proc-receive hook (alt <ref>, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"alt refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report alt (alt <ref>, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> alt refs/for/master/topic\n+\tremote: error: proc-receive expected \"alt <ref> <alt-ref> ...\", got \"alt refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (alt <ref> <alt-ref>, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"alt refs/for/master/topic refs/pull/123/head\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref>, $PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (alt <ref> <alt-ref> forced-update, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"alt refs/for/master/topic refs/pull/123/head forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> forced-update, $PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (alt <ref> <alt-ref> old-oid=X, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"alt refs/for/master/topic refs/pull/123/head old-oid=$B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> old-oid=X, $PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head old-oid=<COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (alt <ref> old-oid=X, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"alt refs/for/master/topic old-oid=$B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X, $PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (alt <ref> old-oid=X new-oid=Y, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"alt refs/for/master/topic old-oid=$A new-oid=$B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X new-oid=Y, $PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (with multiple alt reports, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/a/b/c/topic\" \\\n+\t\t-r \"alt refs/for/next/topic refs/pull/123/head\" \\\n+\t\t-r \"alt refs/for/master/topic refs/pull/124/head old-oid=$B forced-update new-oid=$A\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: with multiple alt reports ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/for/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/a/b/c/topic\n+\tremote: proc-receive> alt refs/for/next/topic refs/pull/123/head\n+\tremote: proc-receive> alt refs/for/master/topic refs/pull/124/head old-oid=<COMMIT-B> forced-update new-oid=<COMMIT-A>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/for/a/b/c/topic\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0033-report-alt--porcelain.sh b/t/t5411/test-0033-report-alt--porcelain.sh\nnew file mode 100644\nindex 0000000000..49d76892ab\n--- /dev/null\n+++ b/t/t5411/test-0033-report-alt--porcelain.sh\n@@ -0,0 +1,236 @@\n+test_expect_success \"setup proc-receive hook (alt <ref>, --porcelain, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"alt refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report alt (alt <ref>, --porcelain, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> alt refs/for/master/topic\n+\tremote: error: proc-receive expected \"alt <ref> <alt-ref> ...\", got \"alt refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (alt <ref> <alt-ref>, --porcelain, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"alt refs/for/master/topic refs/pull/123/head\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref>, --porcelain, $PROTOCOL)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (alt <ref> <alt-ref> forced-update, --porcelain, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"alt refs/for/master/topic refs/pull/123/head forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> forced-update, --porcelain, $PROTOCOL)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (alt <ref> <alt-ref> old-oid=X, --porcelain, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"alt refs/for/master/topic refs/pull/123/head old-oid=$B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> old-oid=X, --porcelain, $PROTOCOL)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head old-oid=<COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (alt <ref> old-oid=X, --porcelain, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"alt refs/for/master/topic old-oid=$B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X, --porcelain, $PROTOCOL)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (alt <ref> old-oid=X new-oid=Y, --porcelain, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"alt refs/for/master/topic old-oid=$A new-oid=$B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X new-oid=Y, --porcelain, $PROTOCOL)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (with multiple alt reports, --porcelain, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/a/b/c/topic\" \\\n+\t\t-r \"alt refs/for/next/topic refs/pull/123/head\" \\\n+\t\t-r \"alt refs/for/master/topic refs/pull/124/head old-oid=$B forced-update new-oid=$A\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: with multiple alt reports (--porcelain, $PROTOCOL)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/for/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/a/b/c/topic\n+\tremote: proc-receive> alt refs/for/next/topic refs/pull/123/head\n+\tremote: proc-receive> alt refs/for/master/topic refs/pull/124/head old-oid=<COMMIT-B> forced-update new-oid=<COMMIT-A>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/next/topic    [new reference]\n+\t*    HEAD:refs/for/a/b/c/topic    [new reference]\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0034-report-ft.sh b/t/t5411/test-0034-report-ft.sh\nnew file mode 100644\nindex 0000000000..a25fb0505e\n--- /dev/null\n+++ b/t/t5411/test-0034-report-ft.sh\n@@ -0,0 +1,42 @@\n+test_expect_success \"setup proc-receive hook (ft, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ft refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(B)\n+test_expect_success \"proc-receive: fall throught, let receive-pack to execute ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: proc-receive> ft refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] <COMMIT-B> -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/for/master/topic\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             refs/for/master/topic(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/for/master/topic\n+'\ndiff --git a/t/t5411/test-0035-report-ft--porcelain.sh b/t/t5411/test-0035-report-ft--porcelain.sh\nnew file mode 100644\nindex 0000000000..e5c6eae544\n--- /dev/null\n+++ b/t/t5411/test-0035-report-ft--porcelain.sh\n@@ -0,0 +1,43 @@\n+test_expect_success \"setup proc-receive hook (ft, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ft refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(B)\n+test_expect_success \"proc-receive: fall throught, let receive-pack to execute (--porcelain, $PROTOCOL)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\t$B:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: proc-receive> ft refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    <COMMIT-B>:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/for/master/topic\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             refs/for/master/topic(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/for/master/topic\n+'\ndiff --git a/t/t5411/test-0036-report-multi-alt-for-one-ref.sh b/t/t5411/test-0036-report-multi-alt-for-one-ref.sh\nnew file mode 100644\nindex 0000000000..fc019f709b\n--- /dev/null\n+++ b/t/t5411/test-0036-report-multi-alt-for-one-ref.sh\n@@ -0,0 +1,155 @@\n+test_expect_success \"setup proc-receive hook (multiple alt, no alt-ref for the 1st resp, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"alt refs/for/master/topic old-oid=$A new-oid=$B\" \\\n+\t\t-r \"alt refs/for/master/topic refs/changes/24/124/1 old-oid=$ZERO_OID new-oid=$A\" \\\n+\t\t-r \"alt refs/for/master/topic refs/changes/25/125/1 old-oid=$A new-oid=$B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 1st resp ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n+\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 old-oid=<ZERO-OID> new-oid=<COMMIT-A>\n+\tremote: proc-receive> alt refs/for/master/topic refs/changes/25/125/1 old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple alt, no alt-ref for the 2nd resp, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"alt refs/for/master/topic refs/changes/24/124/1 old-oid=$ZERO_OID new-oid=$A\" \\\n+\t\t-r \"alt refs/for/master/topic old-oid=$A new-oid=$B\" \\\n+\t\t-r \"alt refs/for/master/topic refs/changes/25/125/1 old-oid=$B new-oid=$A forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 2nd resp ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 old-oid=<ZERO-OID> new-oid=<COMMIT-A>\n+\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n+\tremote: proc-receive> alt refs/for/master/topic refs/changes/25/125/1 old-oid=<COMMIT-B> new-oid=<COMMIT-A> forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ok and alt for the same ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"alt refs/for/master/topic refs/changes/24/124/1 new-oid=$B old-oid=$A\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report ok and alt for the same ref ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 new-oid=<COMMIT-B> old-oid=<COMMIT-A>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple responses, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"alt refs/for/master/topic refs/changes/23/123/1\" \\\n+\t\t-r \"alt refs/for/master/topic refs/changes/24/124/2 old-oid=$A new-oid=$B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report multiple response ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> alt refs/for/master/topic refs/changes/23/123/1\n+\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/2 old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh\nnew file mode 100644\nindex 0000000000..22607b135e\n--- /dev/null\n+++ b/t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh\n@@ -0,0 +1,159 @@\n+test_expect_success \"setup proc-receive hook (multiple alt, no alt-ref for the 1st resp, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"alt refs/for/master/topic old-oid=$A new-oid=$B\" \\\n+\t\t-r \"alt refs/for/master/topic refs/changes/24/124/1 old-oid=$ZERO_OID new-oid=$A\" \\\n+\t\t-r \"alt refs/for/master/topic refs/changes/25/125/1 old-oid=$A new-oid=$B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 1st resp (--porcelain, $PROTOCOL)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n+\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 old-oid=<ZERO-OID> new-oid=<COMMIT-A>\n+\tremote: proc-receive> alt refs/for/master/topic refs/changes/25/125/1 old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple alt, no alt-ref for the 2nd resp, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"alt refs/for/master/topic refs/changes/24/124/1 old-oid=$ZERO_OID new-oid=$A\" \\\n+\t\t-r \"alt refs/for/master/topic old-oid=$A new-oid=$B\" \\\n+\t\t-r \"alt refs/for/master/topic refs/changes/25/125/1 old-oid=$B new-oid=$A forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 2nd resp (--porcelain, $PROTOCOL)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 old-oid=<ZERO-OID> new-oid=<COMMIT-A>\n+\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n+\tremote: proc-receive> alt refs/for/master/topic refs/changes/25/125/1 old-oid=<COMMIT-B> new-oid=<COMMIT-A> forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ok and alt for the same ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"alt refs/for/master/topic refs/changes/24/124/1 new-oid=$B old-oid=$A\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report ok and alt for the same ref (--porcelain, $PROTOCOL)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 new-oid=<COMMIT-B> old-oid=<COMMIT-A>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple responses, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"alt refs/for/master/topic refs/changes/23/123/1\" \\\n+\t\t-r \"alt refs/for/master/topic refs/changes/24/124/2 old-oid=$A new-oid=$B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report multiple response (--porcelain, $PROTOCOL)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> alt refs/for/master/topic refs/changes/23/123/1\n+\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/2 old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nnew file mode 100644\nindex 0000000000..dd03bf1538\n--- /dev/null\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -0,0 +1,74 @@\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"alt refs/for/master/topic refs/for/master/topic old-oid=$A new-oid=$B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> alt refs/for/master/topic refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t * [new branch] HEAD -> bar\n+\t * [new branch] HEAD -> baz\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new branch] HEAD -> foo\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit update-ref -d refs/heads/foo &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+\n+'\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nnew file mode 100644\nindex 0000000000..e237f40db7\n--- /dev/null\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -0,0 +1,75 @@\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"alt refs/for/master/topic refs/for/master/topic old-oid=$A new-oid=$B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report update of mixed refs (--porcelain, $PROTOCOL)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> alt refs/for/master/topic refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n+\t*    HEAD:refs/heads/bar    [new branch]\n+\t*    HEAD:refs/heads/baz    [new branch]\n+\t*    HEAD:refs/for/next/topic    [new reference]\n+\t*    HEAD:refs/heads/foo    [new branch]\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit update-ref -d refs/heads/foo &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+\n+'\n-- \n2.24.1.15.g448c31058d.agit.4.5\n\n"},{"id":"396353","messageId":"CANYiYbGNTMpdvW7AGs-drOuwhzcR_CJnsCE9TjAWujZtb3cfEg@mail.gmail.com","threadId":"52936","inReplyTo":"xmqqzhbc61jv.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v12 3/7] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-27T17:00:00Z","receivedAt":"2020-04-27T17:01:21Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2020年4月16日周四 上午2:34写道：\n>\n> Jiang Xin <worldhello.net@gmail.com> writes:\n>\n> > Will correct it.\n> >\n> > What I am working on for reroll v13:\n> >\n> > * Add new commit \"receive-pack: feed extended_status to post-receive“\n> >\n> >    When commands are fed to the \"post-receive\" hook, `extended_status` will\n> >    be parsed and the real old-oid, new-oid, reference name will feed to\n> >    the \"post-receive\" hook.\n> >\n> > * Add test cases for \"git push --porcelain\".\n> >\n> >    I found some changes not covered by test.  So I decide add some\n> >    test cases for \"git push --porcelain\".  I will split the test cases into\n> >    multiple files inside \"t5411/\" like:\n> >\n> >        t5411/test-0000-normal-push.sh\n> >        t5411/test-0001-normal-push--porcelain.sh\n> >\n> > * Other minor fixes.\n>\n> Thanks for a heads-up, but given that nobody seems to be reading\n> your patches and commenting on them, you might want to slow down a\n> bit.\n\nFor over a week, I have not received more comments on reroll v13 on\nthis topic (\"jx/proc-receive-hook\"). Therefore, I invite Peff and\nCouder to review because I think it would be very interesting to add a\ncentralized workflow to Github and Gitlab.\n\n * [PATCH v13 3/8]:\nhttps://public-inbox.org/git/20200418160334.15631-4-worldhello.net@gmail.com/\n\nThis patch introduces a new hook \"proc-receive\" on the server side. It\nwon't break anything except GitHub's \"spokes\" architecture (I guess).\nBecause in Alibaba, we have such issue when we implement our own\n\"spokes\" architecture.  In order to make this special push operation\n(on a special ref such as \"refs/for/master/topic\") idempotently on\nmultiple replicas, we extended the protocol of \"receive-pack\" and let\n\"spokes\" to send a request with a flag, which specifies one replica as\nmaster replica to execute the \"proc-receive\" hook.\n\n* [PATCH v13 4/8]:\nhttps://public-inbox.org/git/20200418160334.15631-5-worldhello.net@gmail.com/\n* [PATCH v13 5/8]:\nhttps://public-inbox.org/git/20200418160334.15631-6-worldhello.net@gmail.com/\n\nPatch 4/8 and patch 5/8 extends status report for both server and\nclient sides. The extension is backward compatible. When user push\nlike this:\n\n    git push origin HEAD:refs/for/master/topic\n\nOld version of git may report like:\n\n    To <URL/of/upstream.git>\n     * [new reference] HEAD -> refs/for/master/topic\n\nBut new version of git may report like:\n\n    To <URL/of/upstream.git>\n     + <OID-B>...<OID-A> HEAD -> refs/pull/123/head (forced update)\n\n\nAs for how to use this \"proc-receive\" hook to implement a centralized\ngit workflow, please refer to my blog as a reference:\n\n* https://git-repo.info/en/2020/03/agit-flow-and-git-repo/\n\n--\nJiang Xin\n"},{"id":"396527","messageId":"20200429075604.GA637261@coredump.intra.peff.net","threadId":"52936","inReplyTo":"CANYiYbGNTMpdvW7AGs-drOuwhzcR_CJnsCE9TjAWujZtb3cfEg@mail.gmail.com","subject":"Re: [PATCH v12 3/7] receive-pack: add new proc-receive hook","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-04-29T07:56:04Z","receivedAt":"2020-04-29T07:56:08Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Apr 28, 2020 at 01:00:00AM +0800, Jiang Xin wrote:\n\n> For over a week, I have not received more comments on reroll v13 on\n> this topic (\"jx/proc-receive-hook\"). Therefore, I invite Peff and\n> Couder to review because I think it would be very interesting to add a\n> centralized workflow to Github and Gitlab.\n> \n>  * [PATCH v13 3/8]:\n> https://public-inbox.org/git/20200418160334.15631-4-worldhello.net@gmail.com/\n> \n> This patch introduces a new hook \"proc-receive\" on the server side. It\n> won't break anything except GitHub's \"spokes\" architecture (I guess).\n> Because in Alibaba, we have such issue when we implement our own\n> \"spokes\" architecture.  In order to make this special push operation\n> (on a special ref such as \"refs/for/master/topic\") idempotently on\n> multiple replicas, we extended the protocol of \"receive-pack\" and let\n> \"spokes\" to send a request with a flag, which specifies one replica as\n> master replica to execute the \"proc-receive\" hook.\n\nWe don't do any magic ref-rewriting like this, so we wouldn't turn such\na hook on. Presumably without such a hook, the protocol would remain the\nsame (i.e., if there was no rewrite, then the report-status response\nwouldn't have the extra field).\n\nAnd I'm not sure if you're proposing to also use it as the trigger point\nto coordinate the ref update across replicas. That wouldn't be helpful\nfor our architecture. All Git client connections to GitHub terminate at\na proxy layer that handles load balancing, authentication, and routing.\nFor pushes, that's the layer that multiplexes the packfile to each\nreplica, and it drives the ref update through an out-of-band procedure.\nSo there is no master replica at all; they're all talking equally to the\nproxy layer which coordinates things (and the same is true for non-push\nupdates; they're coordinated by whichever entity is trying to perform\nthe update).\n\nWe do run receive-pack on each replica backend. We have a hacky patch\nfor a config option that tells receive-pack to just skip the actual\nref-transaction, leaving it up to the proxy layer to do. I've been\npushing for us to actually abandon receive-pack entirely, since most of\nits heavy lifting can be done by sub-programs (for-each-ref for the\nadvertisement, index-pack to receive the pack, and update-ref to update\nrefs). But it's a non-trivial change, and the benefits are only\nmoderate, so it hasn't quite been worth the effort yet.\n\nSo I don't really have much of an opinion on it either way.\n\nReading the commit message for patch 3, I do have one small comment. I\nsee this:\n\n  The reporting function from \"receive-pack\" to \"send-pack\" is extended\n  using a backward compatible way by adding key-value pairs after an null\n  character, like:\n  \n      # OK, run this command successfully with optional extended-status.\n      ok <reference>\\0ref=refs/pull/123/head old-oid=...\n  \n      # NO, I reject it.\n      ng <reference> <error message>\n\nbut we should probably avoid trickery like stuffing extra data after a\nNUL byte. We could quite easily extend the protocol with a new\ncapability here. The server advertises \"report-status-v2\" or something,\nand the client responds with the appropriate capability to indicate that\nit understands. And then the new format can be something more\nextensible, like:\n\n  pktline(ref=refs/heads/master)\n  pktline(status=ok)\n  pktline(old-oid=...)\n  pktline(rewrite=refs/pull/123/head)\n  delim-pkt(0001)\n  pktline(ref=refs/heads/other)\n  pktline(status=error)\n  pktline(message=<error message>\n  flush-pkt(0000)\n\nwhich would give us room for more keys later, but without worrying about\nparsing issues after the NUL, running out of room in a pktline if you\nhave too many keys, etc.\n\nI don't have any real thoughts on the hook interface itself. It seems\nlike the ok/no/ft/alt responses would allow just about any hook you'd\nwant. It does seems to also use NUL trickery in the early part. If we're\ndesigning from scratch, I think we should strive for a cleaner protocol.\n\n-Peff\n"},{"id":"396665","messageId":"CANYiYbFCG1_hdy1kA06wjrvGSQ2=y-hJX3Rsh-rn=X9tcqv1VQ@mail.gmail.com","threadId":"52936","inReplyTo":"20200429075604.GA637261@coredump.intra.peff.net","subject":"Re: [PATCH v12 3/7] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-04-30T15:33:27Z","receivedAt":"2020-04-30T15:33:43Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Jeff King <peff@peff.net> 于2020年4月29日周三 下午3:56写道：\n>\n> On Tue, Apr 28, 2020 at 01:00:00AM +0800, Jiang Xin wrote:\n>\n> > For over a week, I have not received more comments on reroll v13 on\n> > this topic (\"jx/proc-receive-hook\"). Therefore, I invite Peff and\n> > Couder to review because I think it would be very interesting to add a\n> > centralized workflow to Github and Gitlab.\n> >\n> >  * [PATCH v13 3/8]:\n> > https://public-inbox.org/git/20200418160334.15631-4-worldhello.net@gmail.com/\n> >\n> > This patch introduces a new hook \"proc-receive\" on the server side. It\n> > won't break anything except GitHub's \"spokes\" architecture (I guess).\n> > Because in Alibaba, we have such issue when we implement our own\n> > \"spokes\" architecture.  In order to make this special push operation\n> > (on a special ref such as \"refs/for/master/topic\") idempotently on\n> > multiple replicas, we extended the protocol of \"receive-pack\" and let\n> > \"spokes\" to send a request with a flag, which specifies one replica as\n> > master replica to execute the \"proc-receive\" hook.\n>\n> We don't do any magic ref-rewriting like this, so we wouldn't turn such\n> a hook on. Presumably without such a hook, the protocol would remain the\n> same (i.e., if there was no rewrite, then the report-status response\n> wouldn't have the extra field).\n>\n> And I'm not sure if you're proposing to also use it as the trigger point\n> to coordinate the ref update across replicas. That wouldn't be helpful\n> for our architecture. All Git client connections to GitHub terminate at\n> a proxy layer that handles load balancing, authentication, and routing.\n> For pushes, that's the layer that multiplexes the packfile to each\n> replica, and it drives the ref update through an out-of-band procedure.\n> So there is no master replica at all; they're all talking equally to the\n> proxy layer which coordinates things (and the same is true for non-push\n> updates; they're coordinated by whichever entity is trying to perform\n> the update).\n>\n> We do run receive-pack on each replica backend. We have a hacky patch\n> for a config option that tells receive-pack to just skip the actual\n> ref-transaction, leaving it up to the proxy layer to do. I've been\n> pushing for us to actually abandon receive-pack entirely, since most of\n> its heavy lifting can be done by sub-programs (for-each-ref for the\n> advertisement, index-pack to receive the pack, and update-ref to update\n> refs). But it's a non-trivial change, and the benefits are only\n> moderate, so it hasn't quite been worth the effort yet.\n>\n\nThanks to Peff for providing technical details of the architecture.  I\nunderstand that \"receive-pack\" of GitHub backend is not involved in\nreferences update (executing the commands), so the \"proc-receive\" hook\nwon't be turned on for GitHub's architecture. While in our\narchitecture (inspired by \"spokes\" of GitHub), the proxy will deliver\nnot only packfile, but also commands to all three replicas. The proxy\nwill execute \"receive-pack\" on the replica with a special argument, so\nthe proxy can talk with \"receive-pack\" with an extended protocol.\nAfter running pre-receive hook and release the packfile from\nquarantine, the replica will stop and wait for the proxy to\ncoordinate. After creating a distributed lock, the proxy will tell all\nthe replicas continue to update the references.  One problem we met is\nthe proc-receive and the post-receive hook must be executed once. We\ncan make the execution of the hooks idempotent, or let only one of the\nreplica run the hook. We choose the latter.\n\n> So I don't really have much of an opinion on it either way.\n>\n> Reading the commit message for patch 3, I do have one small comment. I\n> see this:\n>\n>   The reporting function from \"receive-pack\" to \"send-pack\" is extended\n>   using a backward compatible way by adding key-value pairs after an null\n>   character, like:\n>\n>       # OK, run this command successfully with optional extended-status.\n>       ok <reference>\\0ref=refs/pull/123/head old-oid=...\n>\n>       # NO, I reject it.\n>       ng <reference> <error message>\n>\n> but we should probably avoid trickery like stuffing extra data after a\n> NUL byte. We could quite easily extend the protocol with a new\n> capability here. The server advertises \"report-status-v2\" or something,\n> and the client responds with the appropriate capability to indicate that\n> it understands. And then the new format can be something more\n> extensible, like:\n>\n>   pktline(ref=refs/heads/master)\n>   pktline(status=ok)\n>   pktline(old-oid=...)\n>   pktline(rewrite=refs/pull/123/head)\n>   delim-pkt(0001)\n>   pktline(ref=refs/heads/other)\n>   pktline(status=error)\n>   pktline(message=<error message>\n>   flush-pkt(0000)\n\nThe above status report is from \"receive-pack\" to \"send-pack\", and\nanother place that we need to extend is in `print_helper_status()` of\n`builtin/send-pack.c`, which will send report status in the following\nformat from \"send-pack\" to remote-helper (such as remote-curl.c):\n\n    ok <ref>\n    error <ref> <error message>\n\nBTW, I don't know why not use \"ng\", but use \"error\" here.\n\nI agree that adding new capability (report-status-v2) is a better\nsolution, but I think the above extension is a bit wordy. We add\nadditional 18 characters for each \"ok <ref>\" status, and add\nadditional 32 characters for each \"ng <ref> <msg>\" status. Can we\nextend it like this:\n\n    ok <ref>\n    [optional key-value pairs]\n    ng <ref> <error message>\n    [optional key-value pairs]\n\nE.g.:\n\n    ok refs/heads/master     # no extensions\n    ok refs/for/master/topic  # send 2 commits, and create 2 changes\n    rewrite=refs/changes/123/1\n    new-oid=...\n    # use rewrite key-value pair as delimiter,\n    # because plain text is in use from \"send-pack\" to remote-helper.\n    rewrite=refs/changes/124/1\n    new-oid=...\n    ok refs/heads/next\n    ng refs/heads/release non-fast forward\n\n--\nJiang Xin\n"},{"id":"397091","messageId":"20200505144131.30048-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"CANYiYbFCG1_hdy1kA06wjrvGSQ2=y-hJX3Rsh-rn=X9tcqv1VQ@mail.gmail.com","subject":"[PATCH v14 1/7] transport: not report a non-head push as a branch","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-05T14:41:25Z","receivedAt":"2020-05-05T14:41:44Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing a new reference (not a head or tag), report it as a new\nreference instead of a new branch.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411-proc-receive-hook.sh                  | 106 ++++++++++++++++++\n t/t5411/common-functions.sh                   |  56 +++++++++\n t/t5411/test-0000-standard-git-push.sh        |  84 ++++++++++++++\n .../test-0001-standard-git-push--porcelain.sh |  86 ++++++++++++++\n t/t5411/test-0002-pre-receive-declined.sh     |  33 ++++++\n ...st-0003-pre-receive-declined--porcelain.sh |  34 ++++++\n t/t5516-fetch-push.sh                         |   2 +-\n transport.c                                   |   9 +-\n 8 files changed, 406 insertions(+), 4 deletions(-)\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/test-0000-standard-git-push.sh\n create mode 100644 t/t5411/test-0001-standard-git-push--porcelain.sh\n create mode 100644 t/t5411/test-0002-pre-receive-declined.sh\n create mode 100644 t/t5411/test-0003-pre-receive-declined--porcelain.sh\n\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nnew file mode 100755\nindex 0000000000..67492db940\n--- /dev/null\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -0,0 +1,106 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook'\n+\n+. ./test-lib.sh\n+\n+. \"$TEST_DIRECTORY\"/t5411/common-functions.sh\n+\n+setup_upstream_and_workbench () {\n+\t# Refs of upstream : master(A)\n+\t# Refs of workbench: master(A)  tags/v123\n+\ttest_expect_success \"setup upstream and workbench\" '\n+\t\trm -rf upstream.git &&\n+\t\trm -rf workbench &&\n+\t\tgit init --bare upstream.git &&\n+\t\tgit init workbench &&\n+\t\tcreate_commits_in workbench A B &&\n+\t\t(\n+\t\t\tcd workbench &&\n+\t\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\t\tgit config core.abbrev 7 &&\n+\t\t\tgit remote add origin ../upstream.git &&\n+\t\t\tgit update-ref refs/heads/master $A &&\n+\t\t\tgit tag -m \"v123\" v123 $A &&\n+\t\t\tgit push origin $A:refs/heads/master\n+\t\t) &&\n+\t\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t\t# setup pre-receive hook\n+\t\twrite_script upstream.git/hooks/pre-receive <<-\\EOF &&\n+\t\texec >&2\n+\t\techo \"# pre-receive hook\"\n+\t\twhile read old new ref\n+\t\tdo\n+\t\t\techo \"pre-receive< $old $new $ref\"\n+\t\tdone\n+\t\tEOF\n+\n+\t\t# setup post-receive hook\n+\t\twrite_script upstream.git/hooks/post-receive <<-\\EOF &&\n+\t\texec >&2\n+\t\techo \"# post-receive hook\"\n+\t\twhile read old new ref\n+\t\tdo\n+\t\t\techo \"post-receive< $old $new $ref\"\n+\t\tdone\n+\t\tEOF\n+\n+\t\tupstream=upstream.git\n+\t'\n+}\n+\n+run_proc_receive_hook_test() {\n+\tcase $1 in\n+\thttp)\n+\t\tPROTOCOL=\"HTTP protocol\"\n+\t\tURL_PREFIX=\"http://.*\"\n+\t\t;;\n+\tfile | local)\n+\t\tPROTOCOL=\"builtin protocol\"\n+\t\tURL_PREFIX=\"\\.\\.\"\n+\t\t;;\n+\tesac\n+\n+\t# Include test cases for both file and HTTP protocol\n+\tfor t in  \"$TEST_DIRECTORY\"/t5411/test-*.sh\n+\tdo\n+\t\t. \"$t\"\n+\tdone\n+}\n+\n+# Initialize the upstream repository and local workbench.\n+setup_upstream_and_workbench\n+\n+# Run test cases for 'proc-receive' hook on local file protocol.\n+run_proc_receive_hook_test local\n+\n+ROOT_PATH=\"$PWD\"\n+. \"$TEST_DIRECTORY\"/lib-gpg.sh\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+. \"$TEST_DIRECTORY\"/lib-terminal.sh\n+start_httpd\n+\n+# Re-initialize the upstream repository and local workbench.\n+setup_upstream_and_workbench\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup for HTTP protocol\" '\n+\tgit -C upstream.git config http.receivepack true &&\n+\tupstream=\"$HTTPD_DOCUMENT_ROOT_PATH/upstream.git\" &&\n+\tmv upstream.git \"$upstream\" &&\n+\tset_askpass user@host pass@host &&\n+\tgit -C workbench remote set-url origin $HTTPD_URL/smart/upstream.git\n+'\n+\n+setup_askpass_helper\n+\n+# Run test cases for 'proc-receive' hook on HTTP protocol.\n+run_proc_receive_hook_test http\n+\n+test_done\ndiff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\nnew file mode 100644\nindex 0000000000..6580bebd8e\n--- /dev/null\n+++ b/t/t5411/common-functions.sh\n@@ -0,0 +1,56 @@\n+# Create commits in <repo> and assign each commit's oid to shell variables\n+# given in the arguments (A, B, and C). E.g.:\n+#\n+#     create_commits_in <repo> A B C\n+#\n+# NOTE: Never calling this function from a subshell since variable\n+# assignments will disappear when subshell exits.\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{} --)\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  We can easily prepare the expect text\n+# without having to worry about future changes of the commit ID and spaces\n+# of the output.  Single quotes are replaced with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect text.  We also\n+# remove some locale error messages, which break test if we turn on\n+# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n+# on plumbing commands.\n+make_user_friendly_and_stable_output () {\n+\tsed \\\n+\t\t-e \"s/  *\\$//\" \\\n+\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/'/\\\"/g\" \\\n+\t\t-e \"s/\t/    /g\" \\\n+\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n+\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n+\t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n+\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n+\t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n+\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\" \\\n+\t\t-e \"s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#\" \\\n+\t\t-e \"/^error: / d\"\n+}\ndiff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh\nnew file mode 100644\nindex 0000000000..52160aebf7\n--- /dev/null\n+++ b/t/t5411/test-0000-standard-git-push.sh\n@@ -0,0 +1,84 @@\n+# Refs of upstream : master(A)  \n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t * [new branch] HEAD -> next\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"git-push -f ($PROTOCOL)\" '\n+\tgit -C workbench push -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tHEAD:refs/heads/master \\\n+\t\tHEAD:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo <URL/of/upstream.git>\n+\t + <OID-B>...<OID-A> HEAD -> master (forced update)\n+\t - [deleted] next\n+\t * [new tag] v123 -> v123\n+\t * [new reference] HEAD -> refs/review/master/topic\n+\t * [new branch] HEAD -> a/b/c\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\ndiff --git a/t/t5411/test-0001-standard-git-push--porcelain.sh b/t/t5411/test-0001-standard-git-push--porcelain.sh\nnew file mode 100644\nindex 0000000000..4077015b17\n--- /dev/null\n+++ b/t/t5411/test-0001-standard-git-push--porcelain.sh\n@@ -0,0 +1,86 @@\n+# Refs of upstream : master(A)  \n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"git-push -f ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tHEAD:refs/heads/master \\\n+\t\tHEAD:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo <URL/of/upstream.git>\n+\t+    HEAD:refs/heads/master    <OID-B>...<OID-A> (forced update)\n+\t-    :refs/heads/next    [deleted]\n+\t*    refs/tags/v123:refs/tags/v123    [new tag]\n+\t*    HEAD:refs/review/master/topic    [new reference]\n+\t*    HEAD:refs/heads/a/b/c    [new branch]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\ndiff --git a/t/t5411/test-0002-pre-receive-declined.sh b/t/t5411/test-0002-pre-receive-declined.sh\nnew file mode 100644\nindex 0000000000..c246f7e68e\n--- /dev/null\n+++ b/t/t5411/test-0002-pre-receive-declined.sh\n@@ -0,0 +1,33 @@\n+test_expect_success \"setup pre-receive hook ($PROTOCOL)\" '\n+\tmv \"$upstream/hooks/pre-receive\" \"$upstream/hooks/pre-receive.ok\" &&\n+\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n+\texit 1\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push is declined ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] <COMMIT-B> -> master (pre-receive hook declined)\n+\t ! [remote rejected] HEAD -> next (pre-receive hook declined)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tmv \"$upstream/hooks/pre-receive.ok\" \"$upstream/hooks/pre-receive\"\n+'\ndiff --git a/t/t5411/test-0003-pre-receive-declined--porcelain.sh b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\nnew file mode 100644\nindex 0000000000..510cd3b333\n--- /dev/null\n+++ b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\n@@ -0,0 +1,34 @@\n+test_expect_success \"setup pre-receive hook ($PROTOCOL)\" '\n+\tmv \"$upstream/hooks/pre-receive\" \"$upstream/hooks/pre-receive.ok\" &&\n+\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n+\texit 1\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push is declined ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    <COMMIT-B>:refs/heads/master    [remote rejected] (pre-receive hook declined)\n+\t!    HEAD:refs/heads/next    [remote rejected] (pre-receive hook declined)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tmv \"$upstream/hooks/pre-receive.ok\" \"$upstream/hooks/pre-receive\"\n+'\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 9c6218f568..77938db77f 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1039,7 +1039,7 @@ test_force_fetch_tag \"annotated tag\" \"-f -a -m'tag message'\"\n test_expect_success 'push --porcelain' '\n \tmk_empty testrepo &&\n \techo >.git/foo  \"To testrepo\" &&\n-\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new branch]\"  &&\n+\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new reference]\"  &&\n \techo >>.git/foo \"Done\" &&\n \tgit push >.git/bar --porcelain  testrepo refs/heads/master:refs/remotes/origin/master &&\n \t(\ndiff --git a/transport.c b/transport.c\nindex 15f5ba4e8f..26fefd13c1 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -500,9 +500,12 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\t\t\t porcelain, summary_width);\n \telse if (is_null_oid(&ref->old_oid))\n \t\tprint_ref_status('*',\n-\t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n-\t\t\t\"[new branch]\"),\n-\t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t  ? \"[new tag]\"\n+\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t     ? \"[new branch]\"\n+\t\t\t\t     : \"[new reference]\")),\n+\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"397093","messageId":"20200505144131.30048-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"CANYiYbFCG1_hdy1kA06wjrvGSQ2=y-hJX3Rsh-rn=X9tcqv1VQ@mail.gmail.com","subject":"[PATCH v14 0/7] New proc-receive hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-05T14:41:24Z","receivedAt":"2020-05-05T14:41:44Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThis topic introduces a new hook named \"proc-receive\" for\n\"receive-pack\".  We can use this hook to implement a Gerrit-like\ncentralized workflow.\n\n## Changes since v13\n\n1. Patch 3/7 introduced a new capability \"report-status-v2\" for git-push \n\n   Add new capability \"report-status-v2\" and new report protocol which is\n   not backward compatible for report of git-push.\n   \n   If a user pushes to a pseudo-reference \"refs/for/master/topic\", and\n   \"receive-pack\" creates two new references \"refs/changes/23/123/1\" and\n   \"refs/changes/24/124/1\", for client without the knowledge of\n   \"report-status-v2\", \"receive-pack\" will only send \"ok/ng\" directives in\n   the report, such as:\n   \n       ok ref/for/master/topic\n   \n   But for client which has the knowledge of \"report-status-v2\",\n   \"receive-pack\" will use \"option\" directives to report more attributes\n   for the reference given by the above \"ok/ng\" directive.\n   \n       ok refs/for/master/topic\n       option refname refs/changes/23/123/1\n       option new-oid <new-oid>\n       ok refs/for/master/topic\n       option refname refs/changes/24/124/1\n       option new-oid <new-oid>\n   \n   The client will report two new created references to the end user.\n\n2. Patch 2/7: the \"proc-receive\" reports status using the same way like\n   \"report-status-v2\" to \"receive-pack\".\n\n\n## Range-diff v13...v14\n\n1:  dc7f1d044d ! 1:  9a19919552 transport: not report a non-head push as a branch\n    @@ t/t5411-proc-receive-hook.sh (new)\n     +\tgit -C upstream.git config http.receivepack true &&\n     +\tupstream=\"$HTTPD_DOCUMENT_ROOT_PATH/upstream.git\" &&\n     +\tmv upstream.git \"$upstream\" &&\n    ++\tset_askpass user@host pass@host &&\n     +\tgit -C workbench remote set-url origin $HTTPD_URL/smart/upstream.git\n     +'\n     +\n    @@ t/t5411/test-0000-standard-git-push.sh (new)\n     +# Refs of upstream : master(A)  \n     +# Refs of workbench: master(A)  tags/v123\n     +# git-push         : master(B)             next(A)\n    -+test_expect_success \"non-force git-push ($PROTOCOL)\" '\n    ++test_expect_success \"git-push ($PROTOCOL)\" '\n     +\tgit -C workbench push origin \\\n     +\t\t$B:refs/heads/master \\\n     +\t\tHEAD:refs/heads/next \\\n    @@ t/t5411/test-0000-standard-git-push.sh (new)\n     +# Refs of upstream : master(B)  next(A)\n     +# Refs of workbench: master(A)           tags/v123\n     +# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n    -+test_expect_success \"force git-push ($PROTOCOL)\" '\n    ++test_expect_success \"git-push -f ($PROTOCOL)\" '\n     +\tgit -C workbench push -f origin \\\n     +\t\trefs/tags/v123 \\\n     +\t\t:refs/heads/next \\\n    @@ t/t5411/test-0001-standard-git-push--porcelain.sh (new)\n     +# Refs of upstream : master(A)  \n     +# Refs of workbench: master(A)  tags/v123\n     +# git-push         : master(B)             next(A)\n    -+test_expect_success \"non-force git-push (--porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"git-push ($PROTOCOL/porcelain)\" '\n     +\tgit -C workbench push --porcelain origin \\\n     +\t\t$B:refs/heads/master \\\n     +\t\tHEAD:refs/heads/next \\\n    @@ t/t5411/test-0001-standard-git-push--porcelain.sh (new)\n     +# Refs of upstream : master(B)  next(A)\n     +# Refs of workbench: master(A)           tags/v123\n     +# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n    -+test_expect_success \"force git-push (--porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"git-push -f ($PROTOCOL/porcelain)\" '\n     +\tgit -C workbench push --porcelain -f origin \\\n     +\t\trefs/tags/v123 \\\n     +\t\t:refs/heads/next \\\n    @@ t/t5411/test-0001-standard-git-push--porcelain.sh (new)\n     +\t\tgit update-ref -d refs/heads/a/b/c\n     +\t)\n     +'\n    -+\n     \n      ## t/t5411/test-0002-pre-receive-declined.sh (new) ##\n     @@\n    -+test_expect_success \"setup pre-receive hook (declined, $PROTOCOL)\" '\n    ++test_expect_success \"setup pre-receive hook ($PROTOCOL)\" '\n     +\tmv \"$upstream/hooks/pre-receive\" \"$upstream/hooks/pre-receive.ok\" &&\n     +\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n     +\texit 1\n    @@ t/t5411/test-0002-pre-receive-declined.sh (new)\n     \n      ## t/t5411/test-0003-pre-receive-declined--porcelain.sh (new) ##\n     @@\n    -+test_expect_success \"setup pre-receive hook (declined, $PROTOCOL)\" '\n    ++test_expect_success \"setup pre-receive hook ($PROTOCOL)\" '\n     +\tmv \"$upstream/hooks/pre-receive\" \"$upstream/hooks/pre-receive.ok\" &&\n     +\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n     +\texit 1\n    @@ t/t5411/test-0003-pre-receive-declined--porcelain.sh (new)\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git-push         : master(B)             next(A)\n    -+test_expect_success \"git-push is declined (--porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"git-push is declined ($PROTOCOL/porcelain)\" '\n     +\ttest_must_fail git -C workbench push --porcelain origin \\\n     +\t\t$B:refs/heads/master \\\n     +\t\tHEAD:refs/heads/next \\\n2:  673f44599f < -:  ---------- connect: export parse_feature_value()\n3:  43647b379a ! 2:  f1a3ca9cda receive-pack: add new proc-receive hook\n    @@ Commit message\n             # NO, I reject it.\n             H: PKT-LINE(ng <ref> <reason>)\n             # Fall through, let 'receive-pack' to execute it.\n    -        H: PKT-LINE(ft <ref>)\n    +        H: PKT-LINE(ok <ref>)\n    +        H: PKT-LINE(option fall-through)\n             # OK, but has an alternate reference.  The alternate reference name\n    -        # is given in the third parameter, and other status can be given in\n    -        # key-value pairs.\n    -        H: PKT-LINE(alt <ref> <alt-ref> old-oid=<old-oid> new-oid=<new-oid>\n    -                    forced-update)\n    +        # and other status can be given in options\n    +        H: PKT-LINE(ok <ref>)\n    +        H: PKT-LINE(option refname <refname>)\n    +        H: PKT-LINE(option old-oid <old-oid>)\n    +        H: PKT-LINE(option new-oid <new-oid>)\n    +        H: PKT-LINE(option forced-update)\n             H: ... ...\n             H: flush-pkt\n     \n    @@ Commit message\n         create/update different reference.  For example, a command for a pseudo\n         reference \"refs/for/master/topic\" may create/update different reference\n         such as \"refs/pull/123/head\".  The alternate reference name and other\n    -    status are given in key-value pairs as extended status of the report\n    -    line.\n    +    status are given in option lines.\n     \n         The list of commands returned from \"proc-receive\" will replace the\n         relevant commands that are sent from user to \"receive-pack\", and\n    @@ Commit message\n         other routines.  Finally, the result of the execution of these commands\n         will be reported to end user.\n     \n    -    The reporting function from \"receive-pack\" to \"send-pack\" is extended\n    -    using a backward compatible way by adding key-value pairs after an null\n    -    character, like:\n    -\n    -        # OK, run this command successfully with optional extended-status.\n    -        ok <reference>\\0ref=refs/pull/123/head old-oid=...\n    -\n    -        # NO, I reject it.\n    -        ng <reference> <error message>\n    -\n    -    Parsing the extended status of the report lines for \"send-pack\" is not\n    -    implemented in this commit, so we can make a compatible test on old\n    -    version of a Git client.\n    +    The reporting function from \"receive-pack\" to \"send-pack\" will be\n    +    extended in latter commit just like what the \"proc-receive\" hook reports\n    +    to \"receive-pack\".\n     \n         Suggested-by: Junio C Hamano <gitster@pobox.com>\n         Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n    @@ builtin/receive-pack.c: static void write_head_info(void)\n     +#define RUN_PROC_RECEIVE_RETURNED\t2\n      struct command {\n      \tstruct command *next;\n    - \tconst char *error_string;\n    -+\tconst char *extended_status;\n    +-\tconst char *error_string;\n    ++\tstruct ref_push_report report;\n      \tunsigned int skip_update:1,\n     -\t\t     did_not_exist:1;\n     +\t\t     did_not_exist:1,\n    @@ builtin/receive-pack.c: static void write_head_info(void)\n      \tint index;\n      \tstruct object_id old_oid;\n      \tstruct object_id new_oid;\n    +@@ builtin/receive-pack.c: static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n    + \tstruct command *cmd = state->cmd;\n    + \n    + \twhile (cmd &&\n    +-\t       state->skip_broken && (cmd->error_string || cmd->did_not_exist))\n    ++\t       state->skip_broken && (cmd->report.error_message || cmd->did_not_exist))\n    + \t\tcmd = cmd->next;\n    + \tif (!cmd)\n    + \t\treturn -1; /* EOF */\n     @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n      \treturn finish_command(&proc);\n      }\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +{\n     +\tstruct command *cmd;\n     +\tstruct command *hint = NULL;\n    -+\tstruct strbuf extended_status = STRBUF_INIT;\n     +\tint code = 0;\n    ++\tint new_options = 1;\n     +\n     +\tfor (;;) {\n     +\t\tstruct object_id old_oid, new_oid;\n    -+\t\tconst char *status;\n    -+\t\tchar *refname;\n    -+\t\tchar *remains = NULL;\n    ++\t\tconst char *head;\n    ++\t\tconst char *refname;\n    ++\t\tchar *p;\n     +\n     +\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n     +\t\t\tbreak;\n    -+\t\tstatus = reader->line;\n    -+\t\trefname = strchr(status, ' ');\n    -+\t\tif (refname) {\n    -+\t\t\t*refname++ = '\\0';\n    -+\t\t\tremains = strchr(refname, ' ');\n    -+\t\t\tif (remains)\n    -+\t\t\t\t*remains++ = '\\0';\n    ++\n    ++\t\thead = reader->line;\n    ++\t\tp = strchr(head, ' ');\n    ++\t\tif (!p) {\n    ++\t\t\tstrbuf_addf(errmsg, \"proc-receive reported incomplete status line: '%s'\\n\", head);\n    ++\t\t\tcode = -1;\n    ++\t\t\tcontinue;\n     +\t\t}\n    -+\t\tif (!refname || strlen(refname) == 0) {\n    -+\t\t\tstrbuf_addf(errmsg, \"proc-receive expected '<status> <ref> ...', got '%s'\\n\",\n    -+\t\t\t\t    reader->line);\n    ++\t\t*p++ = '\\0';\n    ++\t\tif (!strcmp(head, \"option\")) {\n    ++\t\t\tstruct ref_push_report_options *options;\n    ++\t\t\tconst char *key, *val;\n    ++\n    ++\t\t\tif (!hint) {\n    ++\t\t\t       if (new_options) {\n    ++\t\t\t\t\tstrbuf_addstr(errmsg, \"proc-receive reported 'option' without a matching 'ok/ng' directive\\n\");\n    ++\t\t\t\t\tnew_options = 0;\n    ++\t\t\t\t}\n    ++\t\t\t\tcode = -1;\n    ++\t\t\t\tcontinue;\n    ++\t\t\t}\n    ++\t\t\toptions = hint->report.options;\n    ++\t\t\twhile (options && options->next)\n    ++\t\t\t\toptions = options->next;\n    ++\t\t\tif (new_options) {\n    ++\t\t\t\tif (!options) {\n    ++\t\t\t\t\thint->report.options = xcalloc(1, sizeof(struct ref_push_report_options));\n    ++\t\t\t\t\toptions = hint->report.options;\n    ++\t\t\t\t} else {\n    ++\t\t\t\t\toptions->next = xcalloc(1, sizeof(struct ref_push_report_options));\n    ++\t\t\t\t\toptions = options->next;\n    ++\t\t\t\t}\n    ++\t\t\t\tnew_options = 0;\n    ++\t\t\t}\n    ++\t\t\tassert(options);\n    ++\t\t\tkey = p;\n    ++\t\t\tp = strchr(key, ' ');\n    ++\t\t\tif (p)\n    ++\t\t\t\t*p++ = '\\0';\n    ++\t\t\tval = p;\n    ++\t\t\tif (!strcmp(key, \"refname\"))\n    ++\t\t\t\toptions->ref_name = xstrdup_or_null(val);\n    ++\t\t\telse if (!strcmp(key, \"old-oid\") && val &&\n    ++\t\t\t\t !parse_oid_hex(val, &old_oid, &val))\n    ++\t\t\t\toptions->old_oid = oiddup(&old_oid);\n    ++\t\t\telse if (!strcmp(key, \"new-oid\") && val &&\n    ++\t\t\t\t !parse_oid_hex(val, &new_oid, &val))\n    ++\t\t\t\toptions->new_oid = oiddup(&new_oid);\n    ++\t\t\telse if (!strcmp(key, \"forced-update\"))\n    ++\t\t\t\toptions->forced_update = 1;\n    ++\t\t\telse if (!strcmp(key, \"fall-through\"))\n    ++\t\t\t\t/* Fall through, let 'receive-pack' to execute it. */\n    ++\t\t\t\thint->run_proc_receive = 0;\n    ++\t\t\tcontinue;\n    ++\t\t}\n    ++\n    ++\t\trefname = p;\n    ++\t\tp = strchr(refname, ' ');\n    ++\t\tif (p)\n    ++\t\t\t*p++ = '\\0';\n    ++\t\tif (strcmp(head, \"ok\") && strcmp(head, \"ng\")) {\n    ++\t\t\tstrbuf_addf(errmsg, \"proc-receive reported bad status '%s' on ref '%s'\\n\",\n    ++\t\t\t\t    head, refname);\n     +\t\t\tcode = -1;\n     +\t\t\tcontinue;\n     +\t\t}\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\tif (!hint) {\n     +\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unknown ref: %s\\n\",\n     +\t\t\t\t    refname);\n    ++\t\t\tcode = -1;\n     +\t\t\tcontinue;\n     +\t\t}\n     +\t\tif (!hint->run_proc_receive) {\n     +\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unexpected ref: %s\\n\",\n     +\t\t\t\t    refname);\n    ++\t\t\tcode = -1;\n     +\t\t\tcontinue;\n     +\t\t}\n    -+\t\tif (!strcmp(status, \"ng\")) {\n    -+\t\t\tif (remains)\n    -+\t\t\t\thint->error_string = xstrdup(remains);\n    ++\t\tif (!strcmp(head, \"ng\")) {\n    ++\t\t\tif (p)\n    ++\t\t\t\thint->report.error_message = xstrdup(p);\n     +\t\t\telse\n    -+\t\t\t\thint->error_string = \"failed\";\n    ++\t\t\t\thint->report.error_message = \"failed\";\n     +\t\t\tcode = -1;\n    -+\t\t} else if (!strcmp(\"ft\", status)) {\n    -+\t\t\t/* Reset \"run_proc_receive\" field, and continue to run in \"receive-pack\" */\n    -+\t\t\thint->run_proc_receive = 0;\n    -+\t\t} else if (!strcmp(\"alt\", status)) {\n    -+\t\t\tchar *alt_ref = NULL;\n    -+\t\t\tchar *p1, *p2;\n    -+\n    -+\t\t\tif (!remains) {\n    -+\t\t\t\tstrbuf_addf(errmsg, \"proc-receive expected 'alt <ref> <alt-ref> ...', got 'alt %s'\\n\",\n    -+\t\t\t\t\t    refname);\n    -+\t\t\t\tcontinue;\n    -+\t\t\t}\n    -+\t\t\tp1 = strchr(remains, '=');\n    -+\t\t\tp2 = strchr(remains, ' ');\n    -+\t\t\tif (!p1) {\n    -+\t\t\t\t/*\n    -+\t\t\t\t * \"alt <ref> <alt-ref>\", or\n    -+\t\t\t\t * \"alt <ref> <alt-ref> forced-update\"\n    -+\t\t\t\t */\n    -+\t\t\t\talt_ref = remains;\n    -+\t\t\t\tremains = p2;\n    -+\t\t\t\tif (remains)\n    -+\t\t\t\t\t*remains++ = '\\0';\n    -+\t\t\t} else if (!p2) {\n    -+\t\t\t\t/* \"alt <ref> old-oid=<oid>\" */\n    -+\t\t\t\talt_ref = NULL;\n    -+\t\t\t} else if (p1 > p2) {\n    -+\t\t\t\t/* \"alt <ref> <alt-ref> old-oid=<oid>\" */\n    -+\t\t\t\talt_ref = remains;\n    -+\t\t\t\tremains = p2;\n    -+\t\t\t\tif (remains)\n    -+\t\t\t\t\t*remains++ = '\\0';\n    -+\t\t\t} else {\n    -+\t\t\t\t/* \"alt <ref> old-oid=<oid1> new-oid=<oid2>\" */\n    -+\t\t\t\talt_ref = NULL;\n    -+\t\t\t}\n    -+\n    -+\t\t\tif (hint->extended_status)\n    -+\t\t\t\tstrbuf_addstr(&extended_status, hint->extended_status);\n    -+\n    -+\t\t\t/*\n    -+\t\t\t * Already received an \"ok <ref>\" response, add an\n    -+\t\t\t * additional \"ref=<ref>\" field.\n    -+\t\t\t */\n    -+\t\t\tif (extended_status.len == 0 &&\n    -+\t\t\t    hint->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)\n    -+\t\t\t\tstrbuf_addf(&extended_status, \"ref=%s\", refname);\n    -+\n    -+\t\t\tif (alt_ref)\n    -+\t\t\t\tstrbuf_addf(&extended_status, \"%sref=%s\",\n    -+\t\t\t\t\t    extended_status.len > 0 ? \" \": \"\",\n    -+\t\t\t\t\t    alt_ref);\n    -+\t\t\telse if (extended_status.len > 0)\n    -+\t\t\t\t/*\n    -+\t\t\t\t * Start new extended status for refname, if it\n    -+\t\t\t\t * is not the first report for this reference.\n    -+\t\t\t\t */\n    -+\t\t\t\tstrbuf_addf(&extended_status, \" ref=%s\", refname);\n    -+\n    -+\t\t\tif (remains) {\n    -+\t\t\t\tconst char *val;\n    -+\t\t\t\tint len;\n    -+\n    -+\t\t\t\tval = parse_feature_value(remains, \"old-oid\", &len);\n    -+\t\t\t\tif (val && len > 0)\n    -+\t\t\t\t\tif (!parse_oid_hex(val, &old_oid, &val))\n    -+\t\t\t\t\t\tstrbuf_addf(&extended_status, \"%sold-oid=%s\",\n    -+\t\t\t\t\t\t\t   extended_status.len > 0 ? \" \": \"\",\n    -+\t\t\t\t\t\t\t   oid_to_hex(&old_oid));\n    -+\n    -+\t\t\t\tval = parse_feature_value(remains, \"new-oid\", &len);\n    -+\t\t\t\tif (val && len > 0)\n    -+\t\t\t\t\tif (!parse_oid_hex(val, &new_oid, &val))\n    -+\t\t\t\t\t\tstrbuf_addf(&extended_status, \"%snew-oid=%s\",\n    -+\t\t\t\t\t\t\t   extended_status.len > 0 ? \" \": \"\",\n    -+\t\t\t\t\t\t\t   oid_to_hex(&new_oid));\n    -+\n    -+\t\t\t\tif (parse_feature_request(remains, \"forced-update\"))\n    -+\t\t\t\t\tstrbuf_addf(&extended_status, \"%sforced-update\",\n    -+\t\t\t\t\t\t    extended_status.len > 0 ? \" \": \"\");\n    -+\t\t\t}\n    -+\n    -+\t\t\tif (extended_status.len > 0) {\n    -+\t\t\t\tfree((void *)hint->extended_status);\n    -+\t\t\t\thint->extended_status = strbuf_detach(&extended_status, NULL);\n    -+\t\t\t}\n    -+\t\t} else if (strcmp(\"ok\", status)) {\n    -+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported bad status '%s' on ref '%s'\\n\",\n    -+\t\t\t\t    status, refname);\n    -+\t\t\tcode = -1;\n    -+\t\t\t/* Skip marking it as RUN_PROC_RECEIVE_RETURNED */\n    -+\t\t\tcontinue;\n     +\t\t}\n    -+\n     +\t\tif (hint->run_proc_receive)\n     +\t\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n    ++\t\tnew_options = 1;\n     +\t}\n     +\n     +\tfor (cmd = commands; cmd; cmd = cmd->next)\n    -+\t\tif (cmd->run_proc_receive && !cmd->error_string &&\n    ++\t\tif (cmd->run_proc_receive && !cmd->report.error_message &&\n     +\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {\n    -+\t\t    cmd->error_string = \"proc-receive failed to report status\";\n    ++\t\t    cmd->report.error_message = \"proc-receive failed to report status\";\n     +\t\t    code = -1;\n     +\t\t}\n    -+\n     +\treturn code;\n     +}\n     +\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\tstruct packet_reader reader;\n     +\tstruct strbuf cap = STRBUF_INIT;\n     +\tstruct strbuf errmsg = STRBUF_INIT;\n    -+\tint pr_use_push_options = 0;\n    ++\tint hook_use_push_options = 0;\n     +\tint version = 0;\n     +\tint code;\n     +\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\t\tif (linelen < reader.pktlen) {\n     +\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n     +\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n    -+\t\t\t\t\tpr_use_push_options = 1;\n    ++\t\t\t\t\thook_use_push_options = 1;\n     +\t\t\t}\n     +\t\t}\n     +\t}\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\n     +\t/* Send commands */\n     +\tfor (cmd = commands; cmd; cmd = cmd->next) {\n    -+\t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)\n    ++\t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->report.error_message)\n     +\t\t\tcontinue;\n     +\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n     +\t\t\t\t oid_to_hex(&cmd->old_oid),\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\tpacket_flush(proc.in);\n     +\n     +\t/* Send push options */\n    -+\tif (pr_use_push_options) {\n    ++\tif (hook_use_push_options) {\n     +\t\tstruct string_list_item *item;\n     +\n     +\t\tfor_each_string_list_item(item, push_options)\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n      static char *refuse_unconfigured_deny_msg =\n      \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n      \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n    +@@ builtin/receive-pack.c: static void run_update_post_hook(struct command *commands)\n    + \t\treturn;\n    + \n    + \tfor (cmd = commands; cmd; cmd = cmd->next) {\n    +-\t\tif (cmd->error_string || cmd->did_not_exist)\n    ++\t\tif (cmd->report.error_message || cmd->did_not_exist)\n    + \t\t\tcontinue;\n    + \t\tif (!proc.args.argc)\n    + \t\t\targv_array_push(&proc.args, hook);\n    +@@ builtin/receive-pack.c: static void check_aliased_update_internal(struct command *cmd,\n    + \tif (!dst_name) {\n    + \t\trp_error(\"refusing update to broken symref '%s'\", cmd->ref_name);\n    + \t\tcmd->skip_update = 1;\n    +-\t\tcmd->error_string = \"broken symref\";\n    ++\t\tcmd->report.error_message = \"broken symref\";\n    + \t\treturn;\n    + \t}\n    + \tdst_name = strip_namespace(dst_name);\n    +@@ builtin/receive-pack.c: static void check_aliased_update_internal(struct command *cmd,\n    + \t\t find_unique_abbrev(&dst_cmd->old_oid, DEFAULT_ABBREV),\n    + \t\t find_unique_abbrev(&dst_cmd->new_oid, DEFAULT_ABBREV));\n    + \n    +-\tcmd->error_string = dst_cmd->error_string =\n    ++\tcmd->report.error_message = dst_cmd->report.error_message =\n    + \t\t\"inconsistent aliased update\";\n    + }\n    + \n    +@@ builtin/receive-pack.c: static void check_aliased_updates(struct command *commands)\n    + \tstring_list_sort(&ref_list);\n    + \n    + \tfor (cmd = commands; cmd; cmd = cmd->next) {\n    +-\t\tif (!cmd->error_string)\n    ++\t\tif (!cmd->report.error_message)\n    + \t\t\tcheck_aliased_update(cmd, &ref_list);\n    + \t}\n    + \n    +@@ builtin/receive-pack.c: static void set_connectivity_errors(struct command *commands,\n    + \t\t\t\t     &opt))\n    + \t\t\tcontinue;\n    + \n    +-\t\tcmd->error_string = \"missing necessary objects\";\n    ++\t\tcmd->report.error_message = \"missing necessary objects\";\n    + \t}\n    + }\n    + \n    +@@ builtin/receive-pack.c: static void reject_updates_to_hidden(struct command *commands)\n    + \tprefix_len = refname_full.len;\n    + \n    + \tfor (cmd = commands; cmd; cmd = cmd->next) {\n    +-\t\tif (cmd->error_string)\n    ++\t\tif (cmd->report.error_message)\n    + \t\t\tcontinue;\n    + \n    + \t\tstrbuf_setlen(&refname_full, prefix_len);\n    +@@ builtin/receive-pack.c: static void reject_updates_to_hidden(struct command *commands)\n    + \t\tif (!ref_is_hidden(cmd->ref_name, refname_full.buf))\n    + \t\t\tcontinue;\n    + \t\tif (is_null_oid(&cmd->new_oid))\n    +-\t\t\tcmd->error_string = \"deny deleting a hidden ref\";\n    ++\t\t\tcmd->report.error_message = \"deny deleting a hidden ref\";\n    + \t\telse\n    +-\t\t\tcmd->error_string = \"deny updating a hidden ref\";\n    ++\t\t\tcmd->report.error_message = \"deny updating a hidden ref\";\n    + \t}\n    + \n    + \tstrbuf_release(&refname_full);\n    +@@ builtin/receive-pack.c: static void reject_updates_to_hidden(struct command *commands)\n    + \n    + static int should_process_cmd(struct command *cmd)\n    + {\n    +-\treturn !cmd->error_string && !cmd->skip_update;\n    ++\treturn !cmd->report.error_message && !cmd->skip_update;\n    + }\n    + \n    + static void warn_if_skipped_connectivity_check(struct command *commands,\n     @@ builtin/receive-pack.c: static void execute_commands_non_atomic(struct command *commands,\n      \tstruct strbuf err = STRBUF_INIT;\n      \n    @@ builtin/receive-pack.c: static void execute_commands_non_atomic(struct command *\n      \t\t\tcontinue;\n      \n      \t\ttransaction = ref_transaction_begin(&err);\n    + \t\tif (!transaction) {\n    + \t\t\trp_error(\"%s\", err.buf);\n    + \t\t\tstrbuf_reset(&err);\n    +-\t\t\tcmd->error_string = \"transaction failed to start\";\n    ++\t\t\tcmd->report.error_message = \"transaction failed to start\";\n    + \t\t\tcontinue;\n    + \t\t}\n    + \n    +-\t\tcmd->error_string = update(cmd, si);\n    ++\t\tcmd->report.error_message = update(cmd, si);\n    + \n    +-\t\tif (!cmd->error_string\n    ++\t\tif (!cmd->report.error_message\n    + \t\t    && ref_transaction_commit(transaction, &err)) {\n    + \t\t\trp_error(\"%s\", err.buf);\n    + \t\t\tstrbuf_reset(&err);\n    +-\t\t\tcmd->error_string = \"failed to update ref\";\n    ++\t\t\tcmd->report.error_message = \"failed to update ref\";\n    + \t\t}\n    + \t\tref_transaction_free(transaction);\n    + \t}\n     @@ builtin/receive-pack.c: static void execute_commands_atomic(struct command *commands,\n      \t}\n      \n    @@ builtin/receive-pack.c: static void execute_commands_atomic(struct command *comm\n     +\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n      \t\t\tcontinue;\n      \n    - \t\tcmd->error_string = update(cmd, si);\n    +-\t\tcmd->error_string = update(cmd, si);\n    ++\t\tcmd->report.error_message = update(cmd, si);\n    + \n    +-\t\tif (cmd->error_string)\n    ++\t\tif (cmd->report.error_message)\n    + \t\t\tgoto failure;\n    + \t}\n    + \n    +@@ builtin/receive-pack.c: static void execute_commands_atomic(struct command *commands,\n    + \n    + failure:\n    + \tfor (cmd = commands; cmd; cmd = cmd->next)\n    +-\t\tif (!cmd->error_string)\n    +-\t\t\tcmd->error_string = reported_error;\n    ++\t\tif (!cmd->report.error_message)\n    ++\t\t\tcmd->report.error_message = reported_error;\n    + \n    + cleanup:\n    + \tref_transaction_free(transaction);\n     @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n      \tstruct iterate_data data;\n      \tstruct async muxer;\n    @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n      \n      \tif (unpacker_error) {\n      \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n    +-\t\t\tcmd->error_string = \"unpacker error\";\n    ++\t\t\tcmd->report.error_message = \"unpacker error\";\n    + \t\treturn;\n    + \t}\n    + \n     @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n      \n      \treject_updates_to_hidden(commands);\n    @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n     +\n      \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n      \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n    - \t\t\tif (!cmd->error_string)\n    +-\t\t\tif (!cmd->error_string)\n    +-\t\t\t\tcmd->error_string = \"pre-receive hook declined\";\n    ++\t\t\tif (!cmd->report.error_message)\n    ++\t\t\t\tcmd->report.error_message = \"pre-receive hook declined\";\n    + \t\t}\n    + \t\treturn;\n    + \t}\n    +@@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n    + \t */\n    + \tif (tmp_objdir_migrate(tmp_objdir) < 0) {\n    + \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n    +-\t\t\tif (!cmd->error_string)\n    +-\t\t\t\tcmd->error_string = \"unable to migrate objects to permanent storage\";\n    ++\t\t\tif (!cmd->report.error_message)\n    ++\t\t\t\tcmd->report.error_message = \"unable to migrate objects to permanent storage\";\n    + \t\t}\n    + \t\treturn;\n    + \t}\n     @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n      \tfree(head_name_to_free);\n      \thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, NULL, NULL);\n    @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n     +\tif (run_proc_receive &&\n     +\t    run_proc_receive_hook(commands, push_options))\n     +\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n    -+\t\t\tif (!cmd->error_string  && (cmd->run_proc_receive || use_atomic))\n    -+\t\t\t\tcmd->error_string = \"fail to run proc-receive hook\";\n    ++\t\t\tif (!cmd->report.error_message  && (cmd->run_proc_receive || use_atomic))\n    ++\t\t\t\tcmd->report.error_message = \"fail to run proc-receive hook\";\n     +\n      \tif (use_atomic)\n      \t\texecute_commands_atomic(commands, si);\n      \telse\n    +@@ builtin/receive-pack.c: static void update_shallow_info(struct command *commands,\n    + \t\tif (is_null_oid(&cmd->new_oid))\n    + \t\t\tcontinue;\n    + \t\tif (ref_status[cmd->index]) {\n    +-\t\t\tcmd->error_string = \"shallow update not allowed\";\n    ++\t\t\tcmd->report.error_message = \"shallow update not allowed\";\n    + \t\t\tcmd->skip_update = 1;\n    + \t\t}\n    + \t}\n     @@ builtin/receive-pack.c: static void report(struct command *commands, const char *unpack_status)\n      \tpacket_buf_write(&buf, \"unpack %s\\n\",\n      \t\t\t unpack_status ? unpack_status : \"ok\");\n      \tfor (cmd = commands; cmd; cmd = cmd->next) {\n     -\t\tif (!cmd->error_string)\n    --\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n    ++\t\tif (!cmd->report.error_message)\n    + \t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n     -\t\t\t\t\t cmd->ref_name);\n    --\t\telse\n    --\t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n    --\t\t\t\t\t cmd->ref_name, cmd->error_string);\n    -+\t\tif (!cmd->error_string) {\n    -+\t\t\tif (cmd->extended_status)\n    -+\t\t\t\tpacket_buf_write(&buf, \"ok %s%c%s\\n\",\n    -+\t\t\t\t\t\t cmd->ref_name, '\\0',\n    -+\t\t\t\t\t\t cmd->extended_status);\n    -+\t\t\telse\n    -+\t\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n     +\t\t\t\t\t\t cmd->ref_name);\n    -+\t\t} else {\n    -+\t\t\tif (cmd->extended_status)\n    -+\t\t\t\tpacket_buf_write(&buf, \"ng %s %s%c%s\\n\",\n    -+\t\t\t\t\t\t cmd->ref_name, cmd->error_string,\n    -+\t\t\t\t\t\t '\\0', cmd->extended_status);\n    -+\t\t\telse\n    -+\t\t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n    -+\t\t\t\t\t\t cmd->ref_name, cmd->error_string);\n    -+\t\t}\n    + \t\telse\n    + \t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n    +-\t\t\t\t\t cmd->ref_name, cmd->error_string);\n    ++\t\t\t\t\t cmd->ref_name, cmd->report.error_message);\n      \t}\n      \tpacket_buf_flush(&buf);\n      \n    +@@ builtin/receive-pack.c: int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n    + \t\tif (!check_cert_push_options(&push_options)) {\n    + \t\t\tstruct command *cmd;\n    + \t\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n    +-\t\t\t\tcmd->error_string = \"inconsistent push options\";\n    ++\t\t\t\tcmd->report.error_message = \"inconsistent push options\";\n    + \t\t}\n    + \n    + \t\tprepare_shallow_info(&si, &shallow);\n    +\n    + ## remote.h ##\n    +@@ remote.h: int for_each_remote(each_remote_fn fn, void *priv);\n    + \n    + int remote_has_url(struct remote *remote, const char *url);\n    + \n    ++struct ref_push_report_options {\n    ++\tchar *ref_name;\n    ++\tstruct object_id *old_oid;\n    ++\tstruct object_id *new_oid;\n    ++\tunsigned int forced_update:1;\n    ++\tstruct ref_push_report_options *next;\n    ++};\n    ++\n    ++struct ref_push_report {\n    ++\tconst char *message;\n    ++\tconst char *error_message;\n    ++\tstruct ref_push_report_options *options;\n    ++};\n    ++\n    + struct ref {\n    + \tstruct ref *next;\n    + \tstruct object_id old_oid;\n     \n      ## t/helper/test-proc-receive.c (new) ##\n     @@\n    @@ t/t5411/test-0012-no-hook-error--porcelain.sh (new)\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       next(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: no hook, fail to push special ref (--porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL/porcelain)\" '\n     +\ttest_must_fail git -C workbench push --porcelain origin \\\n     +\t\tHEAD:next \\\n     +\t\tHEAD:refs/for/master/topic \\\n    @@ t/t5411/test-0012-no-hook-error--porcelain.sh (new)\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: no hook, all failed for atomic push (--porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCOL/porcelain)\" '\n     +\ttest_must_fail git -C workbench push --porcelain --atomic origin \\\n     +\t\t$B:master \\\n     +\t\tHEAD:next \\\n    @@ t/t5411/test-0013-bad-protocol.sh (new)\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> ok\n    -+\tremote: error: proc-receive expected \"<status> <ref> ...\", got \"ok\"\n    ++\tremote: error: proc-receive reported incomplete status line: \"ok\"\n     +\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n     +\tEOF\n    @@ t/t5411/test-0014-bad-protocol--porcelain.sh (new)\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: bad protocol (unknown version, --porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL/porcelain)\" '\n     +\ttest_must_fail git -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0014-bad-protocol--porcelain.sh (new)\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       next(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: bad protocol (no report, --porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL/porcelain)\" '\n     +\ttest_must_fail git -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/heads/next \\\n     +\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n    @@ t/t5411/test-0014-bad-protocol--porcelain.sh (new)\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic\n    -+test_expect_success \"proc-receive: bad protocol (no ref, --porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)\" '\n     +\ttest_must_fail git -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/master/topic\\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0014-bad-protocol--porcelain.sh (new)\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> ok\n    -+\tremote: error: proc-receive expected \"<status> <ref> ...\", got \"ok\"\n    ++\tremote: error: proc-receive reported incomplete status line: \"ok\"\n     +\tTo <URL/of/upstream.git>\n     +\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n     +\tDone\n    @@ t/t5411/test-0014-bad-protocol--porcelain.sh (new)\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic\n    -+test_expect_success \"proc-receive: bad protocol (unknown status, --porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL/porcelain)\" '\n     +\ttest_must_fail git -C workbench push --porcelain origin \\\n     +\t\t\tHEAD:refs/for/master/topic \\\n     +\t\t\t>out 2>&1 &&\n    @@ t/t5411/test-0021-report-ng--porcelain.sh (new)\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic\n    -+test_expect_success \"proc-receive: fail to update (ng, no message, --porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL/porcelain)\" '\n     +\ttest_must_fail git -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0021-report-ng--porcelain.sh (new)\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic\n    -+test_expect_success \"proc-receive: fail to update (ng, with message, --porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL/porcelain)\" '\n     +\ttest_must_fail git -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0023-report-unexpect-ref--porcelain.sh (new)\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         : (B)                   refs/for/master/topic\n    -+test_expect_success \"proc-receive: report unexpected ref (--porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL/porcelain)\" '\n     +\ttest_must_fail git -C workbench push --porcelain origin \\\n     +\t\t$B:refs/heads/master \\\n     +\t\tHEAD:refs/for/master/topic \\\n    @@ t/t5411/test-0025-report-unknown-ref--porcelain.sh (new)\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/a/b/c/my/topic\n    -+test_expect_success \"proc-receive: report unknown reference (--porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: report unknown reference ($PROTOCOL/porcelain)\" '\n     +\ttest_must_fail git -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/a/b/c/my/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0027-push-options--porcelain.sh (new)\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push -o ...  :                       refs/for/master/topic\n    -+test_expect_success \"proc-receive: not support push options (--porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: not support push options ($PROTOCOL/porcelain)\" '\n     +\ttest_must_fail git -C workbench push \\\n     +\t\t--porcelain \\\n     +\t\t-o issue=123 \\\n    @@ t/t5411/test-0027-push-options--porcelain.sh (new)\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push -o ...  :                       next(A)  refs/for/master/topic\n    -+test_expect_success \"proc-receive: push with options (--porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: push with options ($PROTOCOL/porcelain)\" '\n     +\tgit -C workbench push \\\n     +\t\t--porcelain \\\n     +\t\t--atomic \\\n    @@ t/t5411/test-0031-report-ok--porcelain.sh (new)\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic\n    -+test_expect_success \"proc-receive: ok (--porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: ok ($PROTOCOL/porcelain)\" '\n     +\tgit -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0031-report-ok--porcelain.sh (new)\n     +\ttest_cmp expect actual\n     +'\n     \n    - ## t/t5411/test-0032-report-alt.sh (new) ##\n    + ## t/t5411/test-0032-report-with-options.sh (new) ##\n     @@\n    -+test_expect_success \"setup proc-receive hook (alt <ref>, $PROTOCOL)\" '\n    ++test_expect_success \"setup proc-receive hook (option without matching ok, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"alt refs/for/master/topic\"\n    ++\t\t-r \"option refname refs/pull/123/head\" \\\n    ++\t\t-r \"option old-oid $B\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report alt (alt <ref>, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: report option without matching ok ($PROTOCOL)\" '\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0032-report-alt.sh (new)\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic\n    -+\tremote: error: proc-receive expected \"alt <ref> <alt-ref> ...\", got \"alt refs/for/master/topic\"\n    ++\tremote: proc-receive> option refname refs/pull/123/head\n    ++\tremote: proc-receive> option old-oid <COMMIT-B>\n    ++\tremote: error: proc-receive reported \"option\" without a matching \"ok/ng\" directive\n     +\tTo <URL/of/upstream.git>\n     +\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n     +\tEOF\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (alt <ref> <alt-ref>, $PROTOCOL)\" '\n    ++test_expect_success \"setup proc-receive hook (option refname, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"alt refs/for/master/topic refs/pull/123/head\"\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/pull/123/head\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref>, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: report option refname ($PROTOCOL)\" '\n     +\tgit -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0032-report-alt.sh (new)\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/pull/123/head\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    @@ t/t5411/test-0032-report-alt.sh (new)\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (alt <ref> <alt-ref> forced-update, $PROTOCOL)\" '\n    ++test_expect_success \"setup proc-receive hook (option refname and forced-update, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"alt refs/for/master/topic refs/pull/123/head forced-update\"\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/pull/123/head\" \\\n    ++\t\t-r \"option forced-update\"\n     +\tEOF\n     +'\n    -+\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> forced-update, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: report option refname and forced-update ($PROTOCOL)\" '\n     +\tgit -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0032-report-alt.sh (new)\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head forced-update\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/pull/123/head\n    ++\tremote: proc-receive> option forced-update\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    @@ t/t5411/test-0032-report-alt.sh (new)\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (alt <ref> <alt-ref> old-oid=X, $PROTOCOL)\" '\n    ++test_expect_success \"setup proc-receive hook (option refname and old-oid, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"alt refs/for/master/topic refs/pull/123/head old-oid=$B\"\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/pull/123/head\" \\\n    ++\t\t-r \"option old-oid $B\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> old-oid=X, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL)\" '\n     +\tgit -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0032-report-alt.sh (new)\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head old-oid=<COMMIT-B>\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/pull/123/head\n    ++\tremote: proc-receive> option old-oid <COMMIT-B>\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    @@ t/t5411/test-0032-report-alt.sh (new)\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (alt <ref> old-oid=X, $PROTOCOL)\" '\n    ++test_expect_success \"setup proc-receive hook (option old-oid, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"alt refs/for/master/topic old-oid=$B\"\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option old-oid $B\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: report option old-oid ($PROTOCOL)\" '\n     +\tgit -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0032-report-alt.sh (new)\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-B>\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option old-oid <COMMIT-B>\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    @@ t/t5411/test-0032-report-alt.sh (new)\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (alt <ref> old-oid=X new-oid=Y, $PROTOCOL)\" '\n    ++test_expect_success \"setup proc-receive hook (option old-oid and new-oid, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"alt refs/for/master/topic old-oid=$A new-oid=$B\"\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option old-oid $A\" \\\n    ++\t\t-r \"option new-oid $B\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X new-oid=Y, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL)\" '\n     +\tgit -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0032-report-alt.sh (new)\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option old-oid <COMMIT-A>\n    ++\tremote: proc-receive> option new-oid <COMMIT-B>\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    @@ t/t5411/test-0032-report-alt.sh (new)\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (with multiple alt reports, $PROTOCOL)\" '\n    ++test_expect_success \"setup proc-receive hook (report with multiple rewrites, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n     +\t\t-r \"ok refs/for/a/b/c/topic\" \\\n    -+\t\t-r \"alt refs/for/next/topic refs/pull/123/head\" \\\n    -+\t\t-r \"alt refs/for/master/topic refs/pull/124/head old-oid=$B forced-update new-oid=$A\"\n    ++\t\t-r \"ok refs/for/next/topic\" \\\n    ++\t\t-r \"option refname refs/pull/123/head\" \\\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/pull/124/head\" \\\n    ++\t\t-r \"option old-oid $B\" \\\n    ++\t\t-r \"option forced-update\" \\\n    ++\t\t-r \"option new-oid $A\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: with multiple alt reports ($PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n     +\tgit -C workbench push origin \\\n     +\t\tHEAD:refs/for/next/topic \\\n     +\t\tHEAD:refs/for/a/b/c/topic \\\n    @@ t/t5411/test-0032-report-alt.sh (new)\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> ok refs/for/a/b/c/topic\n    -+\tremote: proc-receive> alt refs/for/next/topic refs/pull/123/head\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/pull/124/head old-oid=<COMMIT-B> forced-update new-oid=<COMMIT-A>\n    ++\tremote: proc-receive> ok refs/for/next/topic\n    ++\tremote: proc-receive> option refname refs/pull/123/head\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/pull/124/head\n    ++\tremote: proc-receive> option old-oid <COMMIT-B>\n    ++\tremote: proc-receive> option forced-update\n    ++\tremote: proc-receive> option new-oid <COMMIT-A>\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n    @@ t/t5411/test-0032-report-alt.sh (new)\n     +\ttest_cmp expect actual\n     +'\n     \n    - ## t/t5411/test-0033-report-alt--porcelain.sh (new) ##\n    + ## t/t5411/test-0033-report-with-options--porcelain.sh (new) ##\n     @@\n    -+test_expect_success \"setup proc-receive hook (alt <ref>, --porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"setup proc-receive hook (option without matching ok, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"alt refs/for/master/topic\"\n    ++\t\t-r \"option refname refs/pull/123/head\" \\\n    ++\t\t-r \"option old-oid $B\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report alt (alt <ref>, --porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: report option without matching ok ($PROTOCOL/porcelain)\" '\n     +\ttest_must_fail git -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic\n    -+\tremote: error: proc-receive expected \"alt <ref> <alt-ref> ...\", got \"alt refs/for/master/topic\"\n    ++\tremote: proc-receive> option refname refs/pull/123/head\n    ++\tremote: proc-receive> option old-oid <COMMIT-B>\n    ++\tremote: error: proc-receive reported \"option\" without a matching \"ok/ng\" directive\n     +\tTo <URL/of/upstream.git>\n     +\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n     +\tDone\n    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (alt <ref> <alt-ref>, --porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"setup proc-receive hook (option refname, $PROTOCOL/porcelain)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"alt refs/for/master/topic refs/pull/123/head\"\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/pull/123/head\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref>, --porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: report option refname ($PROTOCOL/porcelain)\" '\n     +\tgit -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/pull/123/head\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (alt <ref> <alt-ref> forced-update, --porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"setup proc-receive hook (option refname and forced-update, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"alt refs/for/master/topic refs/pull/123/head forced-update\"\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/pull/123/head\" \\\n    ++\t\t-r \"option forced-update\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> forced-update, --porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: report option refname and forced-update ($PROTOCOL/porcelain)\" '\n     +\tgit -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head forced-update\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/pull/123/head\n    ++\tremote: proc-receive> option forced-update\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (alt <ref> <alt-ref> old-oid=X, --porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"setup proc-receive hook (option refname and old-oid, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"alt refs/for/master/topic refs/pull/123/head old-oid=$B\"\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/pull/123/head\" \\\n    ++\t\t-r \"option old-oid $B\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report alt (alt <ref> <alt-ref> old-oid=X, --porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL/porcelain)\" '\n     +\tgit -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head old-oid=<COMMIT-B>\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/pull/123/head\n    ++\tremote: proc-receive> option old-oid <COMMIT-B>\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (alt <ref> old-oid=X, --porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"setup proc-receive hook (option old-oid, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"alt refs/for/master/topic old-oid=$B\"\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option old-oid $B\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X, --porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: report option old-oid ($PROTOCOL/porcelain)\" '\n     +\tgit -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-B>\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option old-oid <COMMIT-B>\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (alt <ref> old-oid=X new-oid=Y, --porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"setup proc-receive hook (option old-oid and new-oid, $PROTOCOL/porcelain)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"alt refs/for/master/topic old-oid=$A new-oid=$B\"\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option old-oid $A\" \\\n    ++\t\t-r \"option new-oid $B\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report alt (alt <ref> old-oid=X new-oid=Y, --porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL/porcelain)\" '\n     +\tgit -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option old-oid <COMMIT-A>\n    ++\tremote: proc-receive> option new-oid <COMMIT-B>\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (with multiple alt reports, --porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"setup proc-receive hook (report with multiple rewrites, $PROTOCOL/porcelain)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n     +\t\t-r \"ok refs/for/a/b/c/topic\" \\\n    -+\t\t-r \"alt refs/for/next/topic refs/pull/123/head\" \\\n    -+\t\t-r \"alt refs/for/master/topic refs/pull/124/head old-oid=$B forced-update new-oid=$A\"\n    ++\t\t-r \"ok refs/for/next/topic\" \\\n    ++\t\t-r \"option refname refs/pull/123/head\" \\\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/pull/124/head\" \\\n    ++\t\t-r \"option old-oid $B\" \\\n    ++\t\t-r \"option forced-update\" \\\n    ++\t\t-r \"option new-oid $A\"\n    ++\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: with multiple alt reports (--porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porcelain)\" '\n     +\tgit -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/next/topic \\\n     +\t\tHEAD:refs/for/a/b/c/topic \\\n    @@ t/t5411/test-0033-report-alt--porcelain.sh (new)\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> ok refs/for/a/b/c/topic\n    -+\tremote: proc-receive> alt refs/for/next/topic refs/pull/123/head\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/pull/124/head old-oid=<COMMIT-B> forced-update new-oid=<COMMIT-A>\n    ++\tremote: proc-receive> ok refs/for/next/topic\n    ++\tremote: proc-receive> option refname refs/pull/123/head\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/pull/124/head\n    ++\tremote: proc-receive> option old-oid <COMMIT-B>\n    ++\tremote: proc-receive> option forced-update\n    ++\tremote: proc-receive> option new-oid <COMMIT-A>\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n    @@ t/t5411/test-0034-report-ft.sh (new)\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"ft refs/for/master/topic\"\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option fall-through\"\n     +\tEOF\n     +'\n     +\n    @@ t/t5411/test-0034-report-ft.sh (new)\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n    -+\tremote: proc-receive> ft refs/for/master/topic\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option fall-through\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    @@ t/t5411/test-0034-report-ft.sh (new)\n     \n      ## t/t5411/test-0035-report-ft--porcelain.sh (new) ##\n     @@\n    -+test_expect_success \"setup proc-receive hook (ft, $PROTOCOL)\" '\n    ++test_expect_success \"setup proc-receive hook (fall-through, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"ft refs/for/master/topic\"\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option fall-through\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic(B)\n    -+test_expect_success \"proc-receive: fall throught, let receive-pack to execute (--porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: fall throught, let receive-pack to execute ($PROTOCOL/porcelain)\" '\n     +\tgit -C workbench push --porcelain origin \\\n     +\t\t$B:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0035-report-ft--porcelain.sh (new)\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n    -+\tremote: proc-receive> ft refs/for/master/topic\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option fall-through\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    @@ t/t5411/test-0035-report-ft--porcelain.sh (new)\n     +\tgit -C \"$upstream\" update-ref -d refs/for/master/topic\n     +'\n     \n    - ## t/t5411/test-0036-report-multi-alt-for-one-ref.sh (new) ##\n    + ## t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh (new) ##\n     @@\n    -+test_expect_success \"setup proc-receive hook (multiple alt, no alt-ref for the 1st resp, $PROTOCOL)\" '\n    ++test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"alt refs/for/master/topic old-oid=$A new-oid=$B\" \\\n    -+\t\t-r \"alt refs/for/master/topic refs/changes/24/124/1 old-oid=$ZERO_OID new-oid=$A\" \\\n    -+\t\t-r \"alt refs/for/master/topic refs/changes/25/125/1 old-oid=$A new-oid=$B\"\n    -+\tEOF\n    -+'\n    -+\n    -+# Refs of upstream : master(A)\n    -+# Refs of workbench: master(A)  tags/v123\n    -+# git push         :                       refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 1st resp ($PROTOCOL)\" '\n    -+\tgit -C workbench push origin \\\n    -+\t\tHEAD:refs/for/master/topic \\\n    -+\t\t>out 2>&1 &&\n    -+\tmake_user_friendly_and_stable_output <out >actual &&\n    -+\tcat >expect <<-EOF &&\n    -+\tremote: # pre-receive hook\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: # proc-receive hook\n    -+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 old-oid=<ZERO-OID> new-oid=<COMMIT-A>\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/changes/25/125/1 old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    -+\tremote: # post-receive hook\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tTo <URL/of/upstream.git>\n    -+\t * [new reference] HEAD -> refs/for/master/topic\n    -+\tEOF\n    -+\ttest_cmp expect actual &&\n    -+\tgit -C \"$upstream\" show-ref >out &&\n    -+\tmake_user_friendly_and_stable_output <out >actual &&\n    -+\tcat >expect <<-EOF &&\n    -+\t<COMMIT-A> refs/heads/master\n    -+\tEOF\n    -+\ttest_cmp expect actual\n    -+'\n    -+\n    -+test_expect_success \"setup proc-receive hook (multiple alt, no alt-ref for the 2nd resp, $PROTOCOL)\" '\n    -+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    -+\tprintf >&2 \"# proc-receive hook\\n\"\n    -+\ttest-tool proc-receive -v \\\n    -+\t\t-r \"alt refs/for/master/topic refs/changes/24/124/1 old-oid=$ZERO_OID new-oid=$A\" \\\n    -+\t\t-r \"alt refs/for/master/topic old-oid=$A new-oid=$B\" \\\n    -+\t\t-r \"alt refs/for/master/topic refs/changes/25/125/1 old-oid=$B new-oid=$A forced-update\"\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option old-oid $A\" \\\n    ++\t\t-r \"option new-oid $B\" \\\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/changes/24/124/1\" \\\n    ++\t\t-r \"option old-oid $ZERO_OID\" \\\n    ++\t\t-r \"option new-oid $A\" \\\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/changes/25/125/1\" \\\n    ++\t\t-r \"option old-oid $A\" \\\n    ++\t\t-r \"option new-oid $B\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 2nd resp ($PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL)\" '\n     +\tgit -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0036-report-multi-alt-for-one-ref.sh (new)\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 old-oid=<ZERO-OID> new-oid=<COMMIT-A>\n    -+\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/changes/25/125/1 old-oid=<COMMIT-B> new-oid=<COMMIT-A> forced-update\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option old-oid <COMMIT-A>\n    ++\tremote: proc-receive> option new-oid <COMMIT-B>\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/changes/24/124/1\n    ++\tremote: proc-receive> option old-oid <ZERO-OID>\n    ++\tremote: proc-receive> option new-oid <COMMIT-A>\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/changes/25/125/1\n    ++\tremote: proc-receive> option old-oid <COMMIT-A>\n    ++\tremote: proc-receive> option new-oid <COMMIT-B>\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    @@ t/t5411/test-0036-report-multi-alt-for-one-ref.sh (new)\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (ok and alt for the same ref, $PROTOCOL)\" '\n    ++test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n     +\t\t-r \"ok refs/for/master/topic\" \\\n    -+\t\t-r \"alt refs/for/master/topic refs/changes/24/124/1 new-oid=$B old-oid=$A\"\n    ++\t\t-r \"option refname refs/changes/24/124/1\" \\\n    ++\t\t-r \"option old-oid $ZERO_OID\" \\\n    ++\t\t-r \"option new-oid $A\" \\\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option old-oid $A\" \\\n    ++\t\t-r \"option new-oid $B\" \\\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/changes/25/125/1\" \\\n    ++\t\t-r \"option old-oid $B\" \\\n    ++\t\t-r \"option new-oid $A\" \\\n    ++\t\t-r \"option forced-update\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report ok and alt for the same ref ($PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL)\" '\n     +\tgit -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0036-report-multi-alt-for-one-ref.sh (new)\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> ok refs/for/master/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 new-oid=<COMMIT-B> old-oid=<COMMIT-A>\n    ++\tremote: proc-receive> option refname refs/changes/24/124/1\n    ++\tremote: proc-receive> option old-oid <ZERO-OID>\n    ++\tremote: proc-receive> option new-oid <COMMIT-A>\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option old-oid <COMMIT-A>\n    ++\tremote: proc-receive> option new-oid <COMMIT-B>\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/changes/25/125/1\n    ++\tremote: proc-receive> option old-oid <COMMIT-B>\n    ++\tremote: proc-receive> option new-oid <COMMIT-A>\n    ++\tremote: proc-receive> option forced-update\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    @@ t/t5411/test-0036-report-multi-alt-for-one-ref.sh (new)\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (multiple responses, $PROTOCOL)\" '\n    ++test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"alt refs/for/master/topic refs/changes/23/123/1\" \\\n    -+\t\t-r \"alt refs/for/master/topic refs/changes/24/124/2 old-oid=$A new-oid=$B\"\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/changes/23/123/1\" \\\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/changes/24/124/2\" \\\n    ++\t\t-r \"option old-oid $A\" \\\n    ++\t\t-r \"option new-oid $B\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report multiple response ($PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n     +\tgit -C workbench push origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0036-report-multi-alt-for-one-ref.sh (new)\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/changes/23/123/1\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/2 old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/changes/23/123/1\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/changes/24/124/2\n    ++\tremote: proc-receive> option old-oid <COMMIT-A>\n    ++\tremote: proc-receive> option new-oid <COMMIT-B>\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    @@ t/t5411/test-0036-report-multi-alt-for-one-ref.sh (new)\n     +\ttest_cmp expect actual\n     +'\n     \n    - ## t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh (new) ##\n    + ## t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh (new) ##\n     @@\n    -+test_expect_success \"setup proc-receive hook (multiple alt, no alt-ref for the 1st resp, $PROTOCOL)\" '\n    -+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    -+\tprintf >&2 \"# proc-receive hook\\n\"\n    -+\ttest-tool proc-receive -v \\\n    -+\t\t-r \"alt refs/for/master/topic old-oid=$A new-oid=$B\" \\\n    -+\t\t-r \"alt refs/for/master/topic refs/changes/24/124/1 old-oid=$ZERO_OID new-oid=$A\" \\\n    -+\t\t-r \"alt refs/for/master/topic refs/changes/25/125/1 old-oid=$A new-oid=$B\"\n    -+\tEOF\n    -+'\n    -+\n    -+# Refs of upstream : master(A)\n    -+# Refs of workbench: master(A)  tags/v123\n    -+# git push         :                       refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 1st resp (--porcelain, $PROTOCOL)\" '\n    -+\tgit -C workbench push --porcelain origin \\\n    -+\t\tHEAD:refs/for/master/topic \\\n    -+\t\t>out 2>&1 &&\n    -+\tmake_user_friendly_and_stable_output <out >actual &&\n    -+\tcat >expect <<-EOF &&\n    -+\tremote: # pre-receive hook\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: # proc-receive hook\n    -+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 old-oid=<ZERO-OID> new-oid=<COMMIT-A>\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/changes/25/125/1 old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    -+\tremote: # post-receive hook\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tTo <URL/of/upstream.git>\n    -+\t*    HEAD:refs/for/master/topic    [new reference]\n    -+\tDone\n    -+\tEOF\n    -+\ttest_cmp expect actual &&\n    -+\tgit -C \"$upstream\" show-ref >out &&\n    -+\tmake_user_friendly_and_stable_output <out >actual &&\n    -+\tcat >expect <<-EOF &&\n    -+\t<COMMIT-A> refs/heads/master\n    -+\tEOF\n    -+\ttest_cmp expect actual\n    -+'\n    -+\n    -+test_expect_success \"setup proc-receive hook (multiple alt, no alt-ref for the 2nd resp, $PROTOCOL)\" '\n    ++test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"alt refs/for/master/topic refs/changes/24/124/1 old-oid=$ZERO_OID new-oid=$A\" \\\n    -+\t\t-r \"alt refs/for/master/topic old-oid=$A new-oid=$B\" \\\n    -+\t\t-r \"alt refs/for/master/topic refs/changes/25/125/1 old-oid=$B new-oid=$A forced-update\"\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option old-oid $A\" \\\n    ++\t\t-r \"option new-oid $B\" \\\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/changes/24/124/1\" \\\n    ++\t\t-r \"option old-oid $ZERO_OID\" \\\n    ++\t\t-r \"option new-oid $A\" \\\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/changes/25/125/1\" \\\n    ++\t\t-r \"option old-oid $A\" \\\n    ++\t\t-r \"option new-oid $B\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report multiple alt, no alt-ref for the 2nd resp (--porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL/porcelain)\" '\n     +\tgit -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh (new)\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 old-oid=<ZERO-OID> new-oid=<COMMIT-A>\n    -+\tremote: proc-receive> alt refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/changes/25/125/1 old-oid=<COMMIT-B> new-oid=<COMMIT-A> forced-update\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option old-oid <COMMIT-A>\n    ++\tremote: proc-receive> option new-oid <COMMIT-B>\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/changes/24/124/1\n    ++\tremote: proc-receive> option old-oid <ZERO-OID>\n    ++\tremote: proc-receive> option new-oid <COMMIT-A>\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/changes/25/125/1\n    ++\tremote: proc-receive> option old-oid <COMMIT-A>\n    ++\tremote: proc-receive> option new-oid <COMMIT-B>\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    @@ t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh (new)\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (ok and alt for the same ref, $PROTOCOL)\" '\n    ++test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n     +\t\t-r \"ok refs/for/master/topic\" \\\n    -+\t\t-r \"alt refs/for/master/topic refs/changes/24/124/1 new-oid=$B old-oid=$A\"\n    ++\t\t-r \"option refname refs/changes/24/124/1\" \\\n    ++\t\t-r \"option old-oid $ZERO_OID\" \\\n    ++\t\t-r \"option new-oid $A\" \\\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option old-oid $A\" \\\n    ++\t\t-r \"option new-oid $B\" \\\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/changes/25/125/1\" \\\n    ++\t\t-r \"option old-oid $B\" \\\n    ++\t\t-r \"option new-oid $A\" \\\n    ++\t\t-r \"option forced-update\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report ok and alt for the same ref (--porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL/porcelain)\" '\n     +\tgit -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh (new)\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> ok refs/for/master/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/1 new-oid=<COMMIT-B> old-oid=<COMMIT-A>\n    ++\tremote: proc-receive> option refname refs/changes/24/124/1\n    ++\tremote: proc-receive> option old-oid <ZERO-OID>\n    ++\tremote: proc-receive> option new-oid <COMMIT-A>\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option old-oid <COMMIT-A>\n    ++\tremote: proc-receive> option new-oid <COMMIT-B>\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/changes/25/125/1\n    ++\tremote: proc-receive> option old-oid <COMMIT-B>\n    ++\tremote: proc-receive> option new-oid <COMMIT-A>\n    ++\tremote: proc-receive> option forced-update\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    @@ t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh (new)\n     +\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success \"setup proc-receive hook (multiple responses, $PROTOCOL)\" '\n    ++test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"alt refs/for/master/topic refs/changes/23/123/1\" \\\n    -+\t\t-r \"alt refs/for/master/topic refs/changes/24/124/2 old-oid=$A new-oid=$B\"\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/changes/23/123/1\" \\\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/changes/24/124/2\" \\\n    ++\t\t-r \"option old-oid $A\" \\\n    ++\t\t-r \"option new-oid $B\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         :                       refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report multiple response (--porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porcelain)\" '\n     +\tgit -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/master/topic \\\n     +\t\t>out 2>&1 &&\n    @@ t/t5411/test-0037-report-multi-alt-for-one-ref--porcelain.sh (new)\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: # proc-receive hook\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/changes/23/123/1\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/changes/24/124/2 old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/changes/23/123/1\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/changes/24/124/2\n    ++\tremote: proc-receive> option old-oid <COMMIT-A>\n    ++\tremote: proc-receive> option new-oid <COMMIT-B>\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n    @@ t/t5411/test-0038-report-mixed-refs.sh (new)\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n     +\t\t-r \"ok refs/for/next/topic\" \\\n    -+\t\t-r \"alt refs/for/master/topic refs/for/master/topic old-oid=$A new-oid=$B\"\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/for/master/topic\" \\\n    ++\t\t-r \"option old-oid $A\" \\\n    ++\t\t-r \"option new-oid $B\"\n     +\tEOF\n     +'\n     +\n    @@ t/t5411/test-0038-report-mixed-refs.sh (new)\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> ok refs/for/next/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/for/master/topic\n    ++\tremote: proc-receive> option old-oid <COMMIT-A>\n    ++\tremote: proc-receive> option new-oid <COMMIT-B>\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n    @@ t/t5411/test-0038-report-mixed-refs.sh (new)\n     +\t\tgit update-ref -d refs/heads/bar &&\n     +\t\tgit update-ref -d refs/heads/baz\n     +\t)\n    -+\n     +'\n     \n      ## t/t5411/test-0039-report-mixed-refs--porcelain.sh (new) ##\n    @@ t/t5411/test-0039-report-mixed-refs--porcelain.sh (new)\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n     +\t\t-r \"ok refs/for/next/topic\" \\\n    -+\t\t-r \"alt refs/for/master/topic refs/for/master/topic old-oid=$A new-oid=$B\"\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/for/master/topic\" \\\n    ++\t\t-r \"option old-oid $A\" \\\n    ++\t\t-r \"option new-oid $B\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n    -+test_expect_success \"proc-receive: report update of mixed refs (--porcelain, $PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcelain)\" '\n     +\tgit -C workbench push --porcelain origin \\\n     +\t\t$B:refs/heads/master \\\n     +\t\tHEAD:refs/heads/bar \\\n    @@ t/t5411/test-0039-report-mixed-refs--porcelain.sh (new)\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive> ok refs/for/next/topic\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/for/master/topic old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/for/master/topic\n    ++\tremote: proc-receive> option old-oid <COMMIT-A>\n    ++\tremote: proc-receive> option new-oid <COMMIT-B>\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n4:  d01004ff52 < -:  ---------- send-pack: extension for client-side status report\n5:  583b5bdfcd < -:  ---------- receive-pack: feed extended_status to post-receive\n-:  ---------- > 3:  267da25afd New capability \"report-status-v2\" for git-push\n-:  ---------- > 4:  2cc92d9383 receive-pack: feed report options to post-receive\n6:  860be91506 = 5:  47c5e59c9a refs.c: refactor to reuse ref_is_hidden()\n7:  9489db75e6 ! 6:  37e041156f receive-pack: new config receive.procReceiveRefs\n    @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n     +\tif (proc_receive_refs.nr > 0) {\n     +\t\tstruct strbuf refname_full = STRBUF_INIT;\n     +\t\tsize_t prefix_len;\n    ++\n    ++\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n    ++\t\tprefix_len = refname_full.len;\n      \n     -\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n     -\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n     -\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n     -\t\t\trun_proc_receive = 1;\n    -+\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n    -+\t\tprefix_len = refname_full.len;\n    -+\n     +\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n     +\t\t\tif (!should_process_cmd(cmd))\n     +\t\t\t\tcontinue;\n    @@ t/t5411/test-0040-process-all-refs.sh (new)\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"ft refs/heads/master\" \\\n    -+\t\t-r \"ft refs/heads/foo\" \\\n    -+\t\t-r \"ft refs/heads/bar\" \\\n    -+\t\t-r \"alt refs/for/master/topic refs/pull/123/head old-oid=$A new-oid=$B \" \\\n    -+\t\t-r \"alt refs/for/next/topic refs/pull/124/head old-oid=$B new-oid=$A forced-update\"\n    ++\t\t-r \"ok refs/heads/master\" \\\n    ++\t\t-r \"option fall-through\" \\\n    ++\t\t-r \"ok refs/heads/foo\" \\\n    ++\t\t-r \"option fall-through\" \\\n    ++\t\t-r \"ok refs/heads/bar\" \\\n    ++\t\t-r \"option fall-through\" \\\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/pull/123/head\" \\\n    ++\t\t-r \"option old-oid $A\" \\\n    ++\t\t-r \"option new-oid $B\" \\\n    ++\t\t-r \"ok refs/for/next/topic\" \\\n    ++\t\t-r \"option refname refs/pull/124/head\" \\\n    ++\t\t-r \"option old-oid $B\" \\\n    ++\t\t-r \"option new-oid $A\" \\\n    ++\t\t-r \"option forced-update\"\n     +\tEOF\n     +'\n     +\n    @@ t/t5411/test-0040-process-all-refs.sh (new)\n     +\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    -+\tremote: proc-receive> ft refs/heads/master\n    -+\tremote: proc-receive> ft refs/heads/foo\n    -+\tremote: proc-receive> ft refs/heads/bar\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    -+\tremote: proc-receive> alt refs/for/next/topic refs/pull/124/head old-oid=<COMMIT-B> new-oid=<COMMIT-A> forced-update\n    ++\tremote: proc-receive> ok refs/heads/master\n    ++\tremote: proc-receive> option fall-through\n    ++\tremote: proc-receive> ok refs/heads/foo\n    ++\tremote: proc-receive> option fall-through\n    ++\tremote: proc-receive> ok refs/heads/bar\n    ++\tremote: proc-receive> option fall-through\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/pull/123/head\n    ++\tremote: proc-receive> option old-oid <COMMIT-A>\n    ++\tremote: proc-receive> option new-oid <COMMIT-B>\n    ++\tremote: proc-receive> ok refs/for/next/topic\n    ++\tremote: proc-receive> option refname refs/pull/124/head\n    ++\tremote: proc-receive> option old-oid <COMMIT-B>\n    ++\tremote: proc-receive> option new-oid <COMMIT-A>\n    ++\tremote: proc-receive> option forced-update\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n     +\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n    @@ t/t5411/test-0041-process-all-refs--porcelain.sh (new)\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"ft refs/heads/master\" \\\n    -+\t\t-r \"ft refs/heads/foo\" \\\n    -+\t\t-r \"ft refs/heads/bar\" \\\n    -+\t\t-r \"alt refs/for/master/topic refs/pull/123/head old-oid=$A new-oid=$B \" \\\n    -+\t\t-r \"alt refs/for/next/topic refs/pull/124/head old-oid=$B new-oid=$A forced-update\"\n    ++\t\t-r \"ok refs/heads/master\" \\\n    ++\t\t-r \"option fall-through\" \\\n    ++\t\t-r \"ok refs/heads/foo\" \\\n    ++\t\t-r \"option fall-through\" \\\n    ++\t\t-r \"ok refs/heads/bar\" \\\n    ++\t\t-r \"option fall-through\" \\\n    ++\t\t-r \"ok refs/for/master/topic\" \\\n    ++\t\t-r \"option refname refs/pull/123/head\" \\\n    ++\t\t-r \"option old-oid $A\" \\\n    ++\t\t-r \"option new-oid $B\" \\\n    ++\t\t-r \"ok refs/for/next/topic\" \\\n    ++\t\t-r \"option refname refs/pull/124/head\" \\\n    ++\t\t-r \"option old-oid $B\" \\\n    ++\t\t-r \"option new-oid $A\" \\\n    ++\t\t-r \"option forced-update\"\n     +\tEOF\n     +'\n     +\n     +# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n    -+test_expect_success \"proc-receive: process all refs (--porcelain) ($PROTOCOL)\" '\n    ++test_expect_success \"proc-receive: process all refs ($PROTOCOL/porcelain)\" '\n     +\tgit -C workbench push --porcelain -f origin \\\n     +\t\tHEAD:refs/heads/master \\\n     +\t\t:refs/heads/foo \\\n    @@ t/t5411/test-0041-process-all-refs--porcelain.sh (new)\n     +\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    -+\tremote: proc-receive> ft refs/heads/master\n    -+\tremote: proc-receive> ft refs/heads/foo\n    -+\tremote: proc-receive> ft refs/heads/bar\n    -+\tremote: proc-receive> alt refs/for/master/topic refs/pull/123/head old-oid=<COMMIT-A> new-oid=<COMMIT-B>\n    -+\tremote: proc-receive> alt refs/for/next/topic refs/pull/124/head old-oid=<COMMIT-B> new-oid=<COMMIT-A> forced-update\n    ++\tremote: proc-receive> ok refs/heads/master\n    ++\tremote: proc-receive> option fall-through\n    ++\tremote: proc-receive> ok refs/heads/foo\n    ++\tremote: proc-receive> option fall-through\n    ++\tremote: proc-receive> ok refs/heads/bar\n    ++\tremote: proc-receive> option fall-through\n    ++\tremote: proc-receive> ok refs/for/master/topic\n    ++\tremote: proc-receive> option refname refs/pull/123/head\n    ++\tremote: proc-receive> option old-oid <COMMIT-A>\n    ++\tremote: proc-receive> option new-oid <COMMIT-B>\n    ++\tremote: proc-receive> ok refs/for/next/topic\n    ++\tremote: proc-receive> option refname refs/pull/124/head\n    ++\tremote: proc-receive> option old-oid <COMMIT-B>\n    ++\tremote: proc-receive> option new-oid <COMMIT-A>\n    ++\tremote: proc-receive> option forced-update\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n     +\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n8:  187a723d71 ! 7:  5e08df63d2 doc: add documentation for the proc-receive hook\n    @@ Documentation/githooks.txt: The default 'update' hook, when enabled--and with\n      `hooks.allowunannotated` config option unset or set to false--prevents\n      unannotated tags to be pushed.\n      \n    ++[[proc-receive]]\n     +proc-receive\n     +~~~~~~~~~~~~\n     +\n    @@ Documentation/githooks.txt: The default 'update' hook, when enabled--and with\n     +    # NO, I reject it.\n     +    H: PKT-LINE(ng <ref> <reason>)\n     +    # Fall through, let 'receive-pack' to execute it.\n    -+    H: PKT-LINE(ft <ref>)\n    ++    H: PKT-LINE(ok <ref>)\n    ++    H: PKT-LINE(option fall-through)\n     +    # OK, but has an alternate reference.  The alternate reference name\n    -+    # is given in the third parameter, and other status can be given in\n    -+    # key-value pairs.\n    -+    H: PKT-LINE(alt <ref> <alt-ref> old-oid=<old-oid> new-oid=<new-oid>\n    -+                forced-update)\n    ++    # and other status can be given in options\n    ++    H: PKT-LINE(ok <ref>)\n    ++    H: PKT-LINE(option refname <refname>)\n    ++    H: PKT-LINE(option old-oid <old-oid>)\n    ++    H: PKT-LINE(option new-oid <new-oid>)\n    ++    H: PKT-LINE(option forced-update)\n     +    H: ... ...\n     +    H: flush-pkt\n     +\n     +Each command for the 'proc-receive' hook may point to a pseudo-reference\n     +and always has a zero-old as its old-oid, while the 'proc-receive' hook\n     +may update an alternate reference and the alternate reference may exist\n    -+already with a non-zero old-oid.  For this case, this hook may return\n    -+different OID and different reference name as extended status of the\n    -+report line.\n    ++already with a non-zero old-oid.  For this case, this hook will use\n    ++\"option\" directives to report extended attributes for the reference given\n    ++by the above \"ok/ng\" directive.\n     +\n     +The report of the commands of this hook should have the same order as\n     +the input.  The exit status of the 'proc-receive' hook only determines\n\n----\n\nJiang Xin (7):\n  transport: not report a non-head push as a branch\n  receive-pack: add new proc-receive hook\n  New capability \"report-status-v2\" for git-push\n  receive-pack: feed report options to post-receive\n  refs.c: refactor to reuse ref_is_hidden()\n  receive-pack: new config receive.procReceiveRefs\n  doc: add documentation for the proc-receive hook\n\n Documentation/config/receive.txt              |  14 +\n Documentation/githooks.txt                    |  62 +++\n Makefile                                      |   1 +\n builtin/receive-pack.c                        | 459 ++++++++++++++++--\n builtin/send-pack.c                           |  21 +-\n refs.c                                        |  13 +-\n refs.h                                        |   1 +\n remote.c                                      |   7 +-\n remote.h                                      |  16 +-\n send-pack.c                                   |  96 +++-\n t/helper/test-proc-receive.c                  | 175 +++++++\n t/helper/test-tool.c                          |   1 +\n t/helper/test-tool.h                          |   1 +\n t/t5411-proc-receive-hook.sh                  | 106 ++++\n t/t5411/common-functions.sh                   |  56 +++\n t/t5411/test-0000-standard-git-push.sh        |  84 ++++\n .../test-0001-standard-git-push--porcelain.sh |  86 ++++\n t/t5411/test-0002-pre-receive-declined.sh     |  33 ++\n ...st-0003-pre-receive-declined--porcelain.sh |  34 ++\n t/t5411/test-0010-proc-receive-settings.sh    |   7 +\n t/t5411/test-0011-no-hook-error.sh            |  64 +++\n t/t5411/test-0012-no-hook-error--porcelain.sh |  66 +++\n t/t5411/test-0013-bad-protocol.sh             | 225 +++++++++\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 160 ++++++\n t/t5411/test-0020-report-ng.sh                |  67 +++\n t/t5411/test-0021-report-ng--porcelain.sh     |  69 +++\n t/t5411/test-0022-report-unexpect-ref.sh      |  45 ++\n ...est-0023-report-unexpect-ref--porcelain.sh |  46 ++\n t/t5411/test-0024-report-unknown-ref.sh       |  34 ++\n ...test-0025-report-unknown-ref--porcelain.sh |  35 ++\n t/t5411/test-0026-push-options.sh             |  79 +++\n t/t5411/test-0027-push-options--porcelain.sh  |  82 ++++\n t/t5411/test-0030-report-ok.sh                |  35 ++\n t/t5411/test-0031-report-ok--porcelain.sh     |  36 ++\n t/t5411/test-0032-report-with-options.sh      | 256 ++++++++++\n ...est-0033-report-with-options--porcelain.sh | 265 ++++++++++\n t/t5411/test-0034-report-ft.sh                |  44 ++\n t/t5411/test-0035-report-ft--porcelain.sh     |  45 ++\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 169 +++++++\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 172 +++++++\n t/t5411/test-0038-report-mixed-refs.sh        |  79 +++\n .../test-0039-report-mixed-refs--porcelain.sh |  81 ++++\n t/t5411/test-0040-process-all-refs.sh         | 113 +++++\n .../test-0041-process-all-refs--porcelain.sh  | 114 +++++\n t/t5516-fetch-push.sh                         |   2 +-\n transport-helper.c                            |  53 +-\n transport.c                                   | 121 +++--\n 47 files changed, 3737 insertions(+), 93 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/test-0000-standard-git-push.sh\n create mode 100644 t/t5411/test-0001-standard-git-push--porcelain.sh\n create mode 100644 t/t5411/test-0002-pre-receive-declined.sh\n create mode 100644 t/t5411/test-0003-pre-receive-declined--porcelain.sh\n create mode 100644 t/t5411/test-0010-proc-receive-settings.sh\n create mode 100644 t/t5411/test-0011-no-hook-error.sh\n create mode 100644 t/t5411/test-0012-no-hook-error--porcelain.sh\n create mode 100644 t/t5411/test-0013-bad-protocol.sh\n create mode 100644 t/t5411/test-0014-bad-protocol--porcelain.sh\n create mode 100644 t/t5411/test-0020-report-ng.sh\n create mode 100644 t/t5411/test-0021-report-ng--porcelain.sh\n create mode 100644 t/t5411/test-0022-report-unexpect-ref.sh\n create mode 100644 t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n create mode 100644 t/t5411/test-0024-report-unknown-ref.sh\n create mode 100644 t/t5411/test-0025-report-unknown-ref--porcelain.sh\n create mode 100644 t/t5411/test-0026-push-options.sh\n create mode 100644 t/t5411/test-0027-push-options--porcelain.sh\n create mode 100644 t/t5411/test-0030-report-ok.sh\n create mode 100644 t/t5411/test-0031-report-ok--porcelain.sh\n create mode 100644 t/t5411/test-0032-report-with-options.sh\n create mode 100644 t/t5411/test-0033-report-with-options--porcelain.sh\n create mode 100644 t/t5411/test-0034-report-ft.sh\n create mode 100644 t/t5411/test-0035-report-ft--porcelain.sh\n create mode 100644 t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n create mode 100644 t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n create mode 100644 t/t5411/test-0038-report-mixed-refs.sh\n create mode 100644 t/t5411/test-0039-report-mixed-refs--porcelain.sh\n create mode 100644 t/t5411/test-0040-process-all-refs.sh\n create mode 100644 t/t5411/test-0041-process-all-refs--porcelain.sh\n\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"397092","messageId":"20200505144131.30048-4-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"CANYiYbFCG1_hdy1kA06wjrvGSQ2=y-hJX3Rsh-rn=X9tcqv1VQ@mail.gmail.com","subject":"[PATCH v14 3/7] New capability \"report-status-v2\" for git-push","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-05T14:41:27Z","receivedAt":"2020-05-05T14:41:48Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThe new introduced \"proc-receive\" hook may handle a command for a\npseudo-reference with a zero-old as its old-oid, while the hook may\ncreate or update a reference with different name, different new-oid,\nand different old-oid (the reference may exist already with a non-zero\nold-oid).  Current \"report-status\" protocol cannot report the status for\nsuch reference rewrite.\n\nAdd new capability \"report-status-v2\" and new report protocol which is\nnot backward compatible for report of git-push.\n\nIf a user pushes to a pseudo-reference \"refs/for/master/topic\", and\n\"receive-pack\" creates two new references \"refs/changes/23/123/1\" and\n\"refs/changes/24/124/1\", for client without the knowledge of\n\"report-status-v2\", \"receive-pack\" will only send \"ok/ng\" directives in\nthe report, such as:\n\n    ok ref/for/master/topic\n\nBut for client which has the knowledge of \"report-status-v2\",\n\"receive-pack\" will use \"option\" directives to report more attributes\nfor the reference given by the above \"ok/ng\" directive.\n\n    ok refs/for/master/topic\n    option refname refs/changes/23/123/1\n    option new-oid <new-oid>\n    ok refs/for/master/topic\n    option refname refs/changes/24/124/1\n    option new-oid <new-oid>\n\nThe client will report two new created references to the end user.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSuggested-by: Jeff King <peff@peff.net>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c                        |  52 +++++++-\n builtin/send-pack.c                           |  21 +++-\n remote.c                                      |   7 +-\n remote.h                                      |   2 +-\n send-pack.c                                   |  96 +++++++++++---\n t/t5411/test-0032-report-with-options.sh      |  14 +--\n ...est-0033-report-with-options--porcelain.sh |  14 +--\n ...t-0036-report-multi-rewrite-for-one-ref.sh |  11 +-\n ...rt-multi-rewrite-for-one-ref--porcelain.sh |  11 +-\n t/t5411/test-0038-report-mixed-refs.sh        |   2 +-\n .../test-0039-report-mixed-refs--porcelain.sh |   2 +-\n transport-helper.c                            |  53 +++++++-\n transport.c                                   | 118 +++++++++++++-----\n 13 files changed, 328 insertions(+), 75 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 7575eec320..8b96a995b8 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -56,6 +56,7 @@ static int advertise_push_options;\n static int unpack_limit = 100;\n static off_t max_input_size;\n static int report_status;\n+static int report_status_v2;\n static int use_sideband;\n static int use_atomic;\n static int use_push_options;\n@@ -239,7 +240,7 @@ static void show_ref(const char *path, const struct object_id *oid)\n \t\tstruct strbuf cap = STRBUF_INIT;\n \n \t\tstrbuf_addstr(&cap,\n-\t\t\t      \"report-status delete-refs side-band-64k quiet\");\n+\t\t\t      \"report-status report-status-v2 delete-refs side-band-64k quiet\");\n \t\tif (advertise_atomic_push)\n \t\t\tstrbuf_addstr(&cap, \" atomic\");\n \t\tif (prefer_ofs_delta)\n@@ -1912,6 +1913,8 @@ static struct command *read_head_info(struct packet_reader *reader,\n \t\t\tconst char *feature_list = reader->line + linelen + 1;\n \t\t\tif (parse_feature_request(feature_list, \"report-status\"))\n \t\t\t\treport_status = 1;\n+\t\t\tif (parse_feature_request(feature_list, \"report-status-v2\"))\n+\t\t\t\treport_status_v2 = 1;\n \t\t\tif (parse_feature_request(feature_list, \"side-band-64k\"))\n \t\t\t\tuse_sideband = LARGE_PACKET_MAX;\n \t\t\tif (parse_feature_request(feature_list, \"quiet\"))\n@@ -2223,6 +2226,49 @@ static void report(struct command *commands, const char *unpack_status)\n \tstrbuf_release(&buf);\n }\n \n+static void report_v2(struct command *commands, const char *unpack_status)\n+{\n+\tstruct command *cmd;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ref_push_report_options *options;\n+\n+\tpacket_buf_write(&buf, \"unpack %s\\n\",\n+\t\t\t unpack_status ? unpack_status : \"ok\");\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tint count = 0;\n+\n+\t\tif (!cmd->report.error_message)\n+\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n+\t\t\t\t\t cmd->ref_name);\n+\t\telse\n+\t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n+\t\t\t\t\t cmd->ref_name,\n+\t\t\t\t\t cmd->report.error_message);\n+\t\tfor (options = cmd->report.options; options; options = options->next) {\n+\t\t\tif (count++ > 0)\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s\\n\", cmd->ref_name);\n+\t\t\tif (options->ref_name)\n+\t\t\t\tpacket_buf_write(&buf, \"option refname %s\\n\",\n+\t\t\t\t\t\t options->ref_name);\n+\t\t\tif (options->old_oid)\n+\t\t\t\tpacket_buf_write(&buf, \"option old-oid %s\\n\",\n+\t\t\t\t\t\t oid_to_hex(options->old_oid));\n+\t\t\tif (options->new_oid)\n+\t\t\t\tpacket_buf_write(&buf, \"option new-oid %s\\n\",\n+\t\t\t\t\t\t oid_to_hex(options->new_oid));\n+\t\t\tif (options->forced_update)\n+\t\t\t\tpacket_buf_write(&buf, \"option forced-update\\n\");\n+\t\t}\n+\t}\n+\tpacket_buf_flush(&buf);\n+\n+\tif (use_sideband)\n+\t\tsend_sideband(1, 1, buf.buf, buf.len, use_sideband);\n+\telse\n+\t\twrite_or_die(1, buf.buf, buf.len);\n+\tstrbuf_release(&buf);\n+}\n+\n static int delete_only(struct command *commands)\n {\n \tstruct command *cmd;\n@@ -2331,7 +2377,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\t\t\t &push_options);\n \t\tif (pack_lockfile)\n \t\t\tunlink_or_warn(pack_lockfile);\n-\t\tif (report_status)\n+\t\tif (report_status_v2)\n+\t\t\treport_v2(commands, unpack_status);\n+\t\telse if (report_status)\n \t\t\treport(commands, unpack_status);\n \t\trun_receive_hook(commands, \"post-receive\", 1,\n \t\t\t\t &push_options);\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex f2c5a34402..4faee72640 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -29,10 +29,12 @@ static struct send_pack_args args;\n static void print_helper_status(struct ref *ref)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ref_push_report_options *options;\n \n \tfor (; ref; ref = ref->next) {\n \t\tconst char *msg = NULL;\n \t\tconst char *res;\n+\t\tint count = 0;\n \n \t\tswitch(ref->status) {\n \t\tcase REF_STATUS_NONE:\n@@ -86,14 +88,29 @@ static void print_helper_status(struct ref *ref)\n \n \t\tstrbuf_reset(&buf);\n \t\tstrbuf_addf(&buf, \"%s %s\", res, ref->name);\n-\t\tif (ref->remote_status)\n-\t\t\tmsg = ref->remote_status;\n+\t\tif (ref->report.error_message)\n+\t\t\tmsg = ref->report.error_message;\n \t\tif (msg) {\n \t\t\tstrbuf_addch(&buf, ' ');\n \t\t\tquote_two_c_style(&buf, \"\", msg, 0);\n \t\t}\n \t\tstrbuf_addch(&buf, '\\n');\n \n+\t\tfor (options = ref->report.options; options; options = options->next) {\n+\t\t\tif (count++ > 0)\n+\t\t\t\tstrbuf_addf(&buf, \"ok %s\\n\", ref->name);\n+\t\t\tif (options->ref_name)\n+\t\t\t\tstrbuf_addf(&buf, \"option refname %s\\n\",\n+\t\t\t\t\t\t  options->ref_name);\n+\t\t\tif (options->old_oid)\n+\t\t\t\tstrbuf_addf(&buf, \"option old-oid %s\\n\",\n+\t\t\t\t\t\t  oid_to_hex(options->old_oid));\n+\t\t\tif (options->new_oid)\n+\t\t\t\tstrbuf_addf(&buf, \"option new-oid %s\\n\",\n+\t\t\t\t\t\t  oid_to_hex(options->new_oid));\n+\t\t\tif (options->forced_update)\n+\t\t\t\tstrbuf_addf(&buf, \"option forced-update\\n\");\n+\t\t}\n \t\twrite_or_die(1, buf.buf, buf.len);\n \t}\n \tstrbuf_release(&buf);\ndiff --git a/remote.c b/remote.c\nindex 534c6426f1..1f5239650d 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"config.h\"\n+#include \"connect.h\"\n #include \"remote.h\"\n #include \"refs.h\"\n #include \"refspec.h\"\n@@ -789,7 +790,7 @@ struct ref *copy_ref(const struct ref *ref)\n \tmemcpy(cpy, ref, len);\n \tcpy->next = NULL;\n \tcpy->symref = xstrdup_or_null(ref->symref);\n-\tcpy->remote_status = xstrdup_or_null(ref->remote_status);\n+\tcpy->report.error_message = xstrdup_or_null(ref->report.error_message);\n \tcpy->peer_ref = copy_ref(ref->peer_ref);\n \treturn cpy;\n }\n@@ -811,7 +812,7 @@ void free_one_ref(struct ref *ref)\n \tif (!ref)\n \t\treturn;\n \tfree_one_ref(ref->peer_ref);\n-\tfree(ref->remote_status);\n+\tfree((void *)ref->report.error_message);\n \tfree(ref->symref);\n \tfree(ref);\n }\n@@ -2299,4 +2300,4 @@ void apply_push_cas(struct push_cas_option *cas,\n \tstruct ref *ref;\n \tfor (ref = remote_refs; ref; ref = ref->next)\n \t\tapply_cas(cas, remote, ref);\n-}\n+}\n\\ No newline at end of file\ndiff --git a/remote.h b/remote.h\nindex 2ed2a16e16..caa3d5c9ba 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -153,7 +153,7 @@ struct ref {\n \t\tREF_STATUS_EXPECTING_REPORT,\n \t\tREF_STATUS_ATOMIC_PUSH_FAILED\n \t} status;\n-\tchar *remote_status;\n+\tstruct ref_push_report report;\n \tstruct ref *peer_ref; /* when renaming */\n \tchar name[FLEX_ARRAY]; /* more */\n };\ndiff --git a/send-pack.c b/send-pack.c\nindex d1b7edc995..9a9285755c 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -153,24 +153,80 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n {\n \tstruct ref *hint;\n \tint ret;\n+\tint new_options = 1;\n \n \thint = NULL;\n \tret = receive_unpack_status(reader);\n \twhile (1) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *head;\n \t\tconst char *refname;\n-\t\tchar *msg;\n+\t\tchar *p;\n+\n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n \t\t\tbreak;\n-\t\tif (!starts_with(reader->line, \"ok \") && !starts_with(reader->line, \"ng \")) {\n+\t\thead = reader->line;\n+\t\tp = strchr(head, ' ');\n+\t\tif (!p) {\n \t\t\terror(\"invalid ref status from remote: %s\", reader->line);\n \t\t\tret = -1;\n \t\t\tbreak;\n \t\t}\n+\t\t*p++ = '\\0';\n+\n+\t\tif (!strcmp(head, \"option\")) {\n+\t\t\tstruct ref_push_report_options *options;\n+\t\t\tconst char *key, *val;\n+\n+\t\t\tif (!hint) {\n+\t\t\t       if (new_options) {\n+\t\t\t\t\terror(\"'option' without a matching 'ok/ng' directive\");\n+\t\t\t\t\tnew_options = 0;\n+\t\t\t\t}\n+\t\t\t\tret = -1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\toptions = hint->report.options;\n+\t\t\twhile (options && options->next)\n+\t\t\t\toptions = options->next;\n+\t\t\tif (new_options) {\n+\t\t\t\tif (!options) {\n+\t\t\t\t\thint->report.options = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\t\toptions = hint->report.options;\n+\t\t\t\t} else {\n+\t\t\t\t\toptions->next = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\t\toptions = options->next;\n+\t\t\t\t}\n+\t\t\t\tnew_options = 0;\n+\t\t\t}\n+\t\t\tassert(options);\n+\t\t\tkey = p;\n+\t\t\tp = strchr(key, ' ');\n+\t\t\tif (p)\n+\t\t\t\t*p++ = '\\0';\n+\t\t\tval = p;\n+\t\t\tif (!strcmp(key, \"refname\"))\n+\t\t\t\toptions->ref_name = xstrdup_or_null(val);\n+\t\t\telse if (!strcmp(key, \"old-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &old_oid, &val))\n+\t\t\t\toptions->old_oid = oiddup(&old_oid);\n+\t\t\telse if (!strcmp(key, \"new-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &new_oid, &val))\n+\t\t\t\toptions->new_oid = oiddup(&new_oid);\n+\t\t\telse if (!strcmp(key, \"forced-update\"))\n+\t\t\t\toptions->forced_update = 1;\n+\t\t\tcontinue;\n+\t\t}\n \n-\t\trefname = reader->line + 3;\n-\t\tmsg = strchr(refname, ' ');\n-\t\tif (msg)\n-\t\t\t*msg++ = '\\0';\n+\t\trefname = p;\n+\t\tp = strchr(refname, ' ');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n+\t\tif (strcmp(head, \"ok\") && strcmp(head, \"ng\")) {\n+\t\t\terror(\"invalid ref status from remote: %s\", reader->line);\n+\t\t\tret = -1;\n+\t\t\tbreak;\n+\t\t}\n \n \t\t/* first try searching at our hint, falling back to all refs */\n \t\tif (hint)\n@@ -182,19 +238,25 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \t\t\t\t\trefname);\n \t\t\tcontinue;\n \t\t}\n-\t\tif (hint->status != REF_STATUS_EXPECTING_REPORT) {\n+\t\tif (hint->status != REF_STATUS_EXPECTING_REPORT &&\n+\t\t    hint->status != REF_STATUS_OK &&\n+\t\t    hint->status != REF_STATUS_REMOTE_REJECT) {\n \t\t\twarning(\"remote reported status on unexpected ref: %s\",\n \t\t\t\t\trefname);\n \t\t\tcontinue;\n \t\t}\n-\n-\t\tif (reader->line[0] == 'o' && reader->line[1] == 'k')\n-\t\t\thint->status = REF_STATUS_OK;\n-\t\telse\n+\t\tif (!strcmp(head, \"ng\")) {\n \t\t\thint->status = REF_STATUS_REMOTE_REJECT;\n-\t\thint->remote_status = xstrdup_or_null(msg);\n-\t\t/* start our next search from the next ref */\n-\t\thint = hint->next;\n+\t\t\tif (p)\n+\t\t\t\thint->report.error_message = xstrdup(p);\n+\t\t\telse\n+\t\t\t\thint->report.error_message = \"failed\";\n+\t\t} else {\n+\t\t\thint->status = REF_STATUS_OK;\n+\t\t\tif (p)\n+\t\t\t\thint->report.message = xstrdup(p);\n+\t\t}\n+\t\tnew_options = 1;\n \t}\n \treturn ret;\n }\n@@ -371,6 +433,8 @@ int send_pack(struct send_pack_args *args,\n \t/* Does the other end support the reporting? */\n \tif (server_supports(\"report-status\"))\n \t\tstatus_report = 1;\n+\tif (server_supports(\"report-status-v2\"))\n+\t\tstatus_report = 2;\n \tif (server_supports(\"delete-refs\"))\n \t\tallow_deleting_refs = 1;\n \tif (server_supports(\"ofs-delta\"))\n@@ -418,8 +482,10 @@ int send_pack(struct send_pack_args *args,\n \n \tuse_push_options = push_options_supported && args->push_options;\n \n-\tif (status_report)\n+\tif (status_report == 1)\n \t\tstrbuf_addstr(&cap_buf, \" report-status\");\n+\telse if (status_report == 2)\n+\t\tstrbuf_addstr(&cap_buf, \" report-status-v2\");\n \tif (use_sideband)\n \t\tstrbuf_addstr(&cap_buf, \" side-band-64k\");\n \tif (quiet_supported && (args->quiet || !args->progress))\ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nindex a743aa8018..3742ed3c3d 100644\n--- a/t/t5411/test-0032-report-with-options.sh\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -56,7 +56,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL)\" '\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -89,7 +89,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -123,7 +123,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL)\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-B>..<OID-A> HEAD -> refs/pull/123/head\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -155,7 +155,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL)\" '\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-B>..<OID-A> HEAD -> refs/for/master/topic\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -189,7 +189,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL)\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -241,9 +241,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \t * [new reference] HEAD -> refs/for/a/b/c/topic\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n \ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nindex 8a50d7efe3..929c46ccd8 100644\n--- a/t/t5411/test-0033-report-with-options--porcelain.sh\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -57,7 +57,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL/porcelain)\"\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -92,7 +92,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -127,7 +127,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL/\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/pull/123/head    <OID-B>..<OID-A>\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -160,7 +160,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL/porcelain)\"\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-B>..<OID-A>\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -195,7 +195,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL/\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -249,9 +249,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porc\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/next/topic    [new reference]\n+\t*    HEAD:refs/pull/123/head    [new reference]\n \t*    HEAD:refs/for/a/b/c/topic    [new reference]\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t+    HEAD:refs/pull/124/head    <OID-B>...<OID-A> (forced update)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex 12acf9ea1f..64662afdc6 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -43,7 +43,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/24/124/1\n+\t <OID-A>..<OID-B> HEAD -> refs/changes/25/125/1\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -101,7 +103,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/24/124/1\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n+\t + <OID-B>...<OID-A> HEAD -> refs/changes/25/125/1 (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -147,7 +151,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/23/123/1\n+\t <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\ndiff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\nindex 57dc0d17ac..a371c8d610 100644\n--- a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n@@ -43,7 +43,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n+\t*    HEAD:refs/changes/24/124/1    [new reference]\n+\t     HEAD:refs/changes/25/125/1    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n@@ -102,7 +104,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/changes/24/124/1    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n+\t+    HEAD:refs/changes/25/125/1    <OID-B>...<OID-A> (forced update)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n@@ -149,7 +153,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porc\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/changes/23/123/1    [new reference]\n+\t     HEAD:refs/changes/24/124/2    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nindex 9853e47693..9b060b2f84 100644\n--- a/t/t5411/test-0038-report-mixed-refs.sh\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -52,7 +52,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n \t * [new branch] HEAD -> baz\n \t * [new reference] HEAD -> refs/for/next/topic\n \t * [new branch] HEAD -> foo\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nindex b3953c749b..537d4046b9 100644\n--- a/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -52,7 +52,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcel\n \t*    HEAD:refs/heads/baz    [new branch]\n \t*    HEAD:refs/for/next/topic    [new reference]\n \t*    HEAD:refs/heads/foo    [new branch]\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\ndiff --git a/transport-helper.c b/transport-helper.c\nindex a46afcb69d..21d6e47966 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -725,6 +725,49 @@ static int push_update_ref_status(struct strbuf *buf,\n {\n \tchar *refname, *msg;\n \tint status, forced = 0;\n+\tstatic struct ref *hint = NULL;\n+\tstatic int new_options = 1;\n+\n+\tif (starts_with(buf->buf, \"option \")) {\n+\t\tstruct ref_push_report_options *options;\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *key, *val;\n+\t\tchar *p;\n+\n+\t\tif (!hint)\n+\t\t\tdie(_(\"'option' without a matching 'ok/error' directive\"));\n+\t\toptions = hint->report.options;\n+\t\twhile (options && options->next)\n+\t\t\toptions = options->next;\n+\t\tif (new_options) {\n+\t\t\tif (!options) {\n+\t\t\t\thint->report.options = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\toptions = hint->report.options;\n+\t\t\t} else {\n+\t\t\t\toptions->next = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\toptions = options->next;\n+\t\t\t}\n+\t\t\tnew_options = 0;\n+\t\t}\n+\t\tassert(options);\n+\t\tkey = buf->buf + 7;\n+\t\tp = strchr(key, ' ');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n+\t\tval = p;\n+\t\tif (!strcmp(key, \"refname\"))\n+\t\t\toptions->ref_name = xstrdup_or_null(val);\n+\t\telse if (!strcmp(key, \"old-oid\") && val &&\n+\t\t\t !parse_oid_hex(val, &old_oid, &val))\n+\t\t\toptions->old_oid = oiddup(&old_oid);\n+\t\telse if (!strcmp(key, \"new-oid\") && val &&\n+\t\t\t !parse_oid_hex(val, &new_oid, &val))\n+\t\t\toptions->new_oid = oiddup(&new_oid);\n+\t\telse if (!strcmp(key, \"forced-update\"))\n+\t\t\toptions->forced_update = 1;\n+\t\t/* Not update remote namespace again. */\n+\t\treturn 1;\n+\t}\n \n \tif (starts_with(buf->buf, \"ok \")) {\n \t\tstatus = REF_STATUS_OK;\n@@ -787,8 +830,11 @@ static int push_update_ref_status(struct strbuf *buf,\n \t\t*ref = find_ref_by_name(remote_refs, refname);\n \tif (!*ref) {\n \t\twarning(_(\"helper reported unexpected status of %s\"), refname);\n+\t\thint = NULL;\n \t\treturn 1;\n \t}\n+\thint = *ref;\n+\tnew_options = 1;\n \n \tif ((*ref)->status != REF_STATUS_NONE) {\n \t\t/*\n@@ -801,7 +847,12 @@ static int push_update_ref_status(struct strbuf *buf,\n \n \t(*ref)->status = status;\n \t(*ref)->forced_update |= forced;\n-\t(*ref)->remote_status = msg;\n+\tif (msg) {\n+\t\tif (status == REF_STATUS_OK)\n+\t\t\t(*ref)->report.message = msg;\n+\t\telse\n+\t\t\t(*ref)->report.error_message = msg;\n+\t}\n \treturn !(status == REF_STATUS_OK);\n }\n \ndiff --git a/transport.c b/transport.c\nindex 26fefd13c1..2e2b7de036 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -461,13 +461,21 @@ void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int v\n \n static void print_ref_status(char flag, const char *summary,\n \t\t\t     struct ref *to, struct ref *from, const char *msg,\n+\t\t\t     struct ref_push_report_options *options,\n \t\t\t     int porcelain, int summary_width)\n {\n+\tchar *to_name;\n+\n+\tif (options && options->ref_name)\n+\t\tto_name = options->ref_name;\n+\telse\n+\t\tto_name = to->name;\n+\n \tif (porcelain) {\n \t\tif (from)\n-\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to_name);\n \t\telse\n-\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to_name);\n \t\tif (msg)\n \t\t\tfprintf(stdout, \"%s (%s)\\n\", summary, msg);\n \t\telse\n@@ -481,9 +489,11 @@ static void print_ref_status(char flag, const char *summary,\n \t\tfprintf(stderr, \" %s%c %-*s%s \", red, flag, summary_width,\n \t\t\tsummary, reset);\n \t\tif (from)\n-\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(to->name));\n+\t\t\tfprintf(stderr, \"%s -> %s\",\n+\t\t\t\tprettify_refname(from->name),\n+\t\t\t\tprettify_refname(to_name));\n \t\telse\n-\t\t\tfputs(prettify_refname(to->name), stderr);\n+\t\t\tfputs(prettify_refname(to_name), stderr);\n \t\tif (msg) {\n \t\t\tfputs(\" (\", stderr);\n \t\t\tfputs(msg, stderr);\n@@ -493,27 +503,52 @@ static void print_ref_status(char flag, const char *summary,\n \t}\n }\n \n-static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_width)\n+static void print_ok_ref_status(struct ref *ref,\n+\t\t\t\tstruct ref_push_report_options *options,\n+\t\t\t\tint porcelain, int summary_width)\n {\n+\tstruct object_id *old_oid;\n+\tstruct object_id *new_oid;\n+\tconst char *ref_name;\n+\tint forced_update;\n+\n+\tif (options && options->old_oid)\n+\t\told_oid = options->old_oid;\n+\telse\n+\t\told_oid = &ref->old_oid;\n+\tif (options && options->new_oid)\n+\t\tnew_oid = options->new_oid;\n+\telse\n+\t\tnew_oid = &ref->new_oid;\n+\tif (options && options->forced_update)\n+\t\tforced_update = options->forced_update;\n+\telse\n+\t\tforced_update = ref->forced_update;\n+\tif (options && options->ref_name)\n+\t\tref_name = options->ref_name;\n+\telse\n+\t\tref_name = ref->name;\n+\n \tif (ref->deletion)\n \t\tprint_ref_status('-', \"[deleted]\", ref, NULL, NULL,\n-\t\t\t\t porcelain, summary_width);\n-\telse if (is_null_oid(&ref->old_oid))\n+\t\t\t\t options, porcelain, summary_width);\n+\telse if (is_null_oid(old_oid))\n \t\tprint_ref_status('*',\n-\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t (starts_with(ref_name, \"refs/tags/\")\n \t\t\t\t  ? \"[new tag]\"\n-\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t  : (starts_with(ref_name, \"refs/heads/\")\n \t\t\t\t     ? \"[new branch]\"\n \t\t\t\t     : \"[new reference]\")),\n-\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t ref, ref->peer_ref, NULL,\n+\t\t\t\t options, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n \t\tconst char *msg;\n \n-\t\tstrbuf_add_unique_abbrev(&quickref, &ref->old_oid,\n+\t\tstrbuf_add_unique_abbrev(&quickref, old_oid,\n \t\t\t\t\t DEFAULT_ABBREV);\n-\t\tif (ref->forced_update) {\n+\t\tif (forced_update) {\n \t\t\tstrbuf_addstr(&quickref, \"...\");\n \t\t\ttype = '+';\n \t\t\tmsg = \"forced update\";\n@@ -522,17 +557,18 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\t\ttype = ' ';\n \t\t\tmsg = NULL;\n \t\t}\n-\t\tstrbuf_add_unique_abbrev(&quickref, &ref->new_oid,\n+\t\tstrbuf_add_unique_abbrev(&quickref, new_oid,\n \t\t\t\t\t DEFAULT_ABBREV);\n \n \t\tprint_ref_status(type, quickref.buf, ref, ref->peer_ref, msg,\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t options, porcelain, summary_width);\n \t\tstrbuf_release(&quickref);\n \t}\n }\n \n-static int print_one_push_status(struct ref *ref, const char *dest, int count,\n-\t\t\t\t int porcelain, int summary_width)\n+static int _print_one_push_status(struct ref *ref, const char *dest, int count,\n+\t\t\t\t  struct ref_push_report_options *options,\n+\t\t\t\t  int porcelain, int summary_width)\n {\n \tif (!count) {\n \t\tchar *url = transport_anonymize_url(dest);\n@@ -543,65 +579,89 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count,\n \tswitch(ref->status) {\n \tcase REF_STATUS_NONE:\n \t\tprint_ref_status('X', \"[no match]\", ref, NULL, NULL,\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NODELETE:\n \t\tprint_ref_status('!', \"[rejected]\", ref, NULL,\n \t\t\t\t \"remote does not support deleting refs\",\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_UPTODATE:\n \t\tprint_ref_status('=', \"[up to date]\", ref,\n-\t\t\t\t ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t ref->peer_ref, NULL,\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NONFASTFORWARD:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"non-fast-forward\", porcelain, summary_width);\n+\t\t\t\t \"non-fast-forward\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"already exists\", porcelain, summary_width);\n+\t\t\t\t \"already exists\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_FETCH_FIRST:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"fetch first\", porcelain, summary_width);\n+\t\t\t\t \"fetch first\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NEEDS_FORCE:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"needs force\", porcelain, summary_width);\n+\t\t\t\t \"needs force\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_STALE:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"stale info\", porcelain, summary_width);\n+\t\t\t\t \"stale info\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_SHALLOW:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n \t\t\t\t \"new shallow roots not allowed\",\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REMOTE_REJECT:\n \t\tprint_ref_status('!', \"[remote rejected]\", ref,\n \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n-\t\t\t\t ref->remote_status, porcelain, summary_width);\n+\t\t\t\t ref->report.error_message,\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_EXPECTING_REPORT:\n \t\tprint_ref_status('!', \"[remote failure]\", ref,\n \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n \t\t\t\t \"remote failed to report status\",\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_ATOMIC_PUSH_FAILED:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"atomic push failed\", porcelain, summary_width);\n+\t\t\t\t \"atomic push failed\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_OK:\n-\t\tprint_ok_ref_status(ref, porcelain, summary_width);\n+\t\tprint_ok_ref_status(ref, options, porcelain, summary_width);\n \t\tbreak;\n \t}\n \n \treturn 1;\n }\n \n+static int print_one_push_status(struct ref *ref, const char *dest, int count,\n+\t\t\t\t int porcelain, int summary_width)\n+{\n+\tstruct ref_push_report_options *options;\n+\tint n = 0;\n+\n+\tif (!ref->report.options)\n+\t\treturn _print_one_push_status(ref, dest, count,\n+\t\t\t\t\t      NULL, porcelain, summary_width);\n+\n+\tfor (options = ref->report.options; options; options = options->next)\n+\t\t_print_one_push_status(ref, dest, count + n++,\n+\t\t\t\t       options, porcelain, summary_width);\n+\treturn n;\n+}\n+\n static int measure_abbrev(const struct object_id *oid, int sofar)\n {\n \tchar hex[GIT_MAX_HEXSZ + 1];\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"397096","messageId":"20200505144131.30048-5-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"CANYiYbFCG1_hdy1kA06wjrvGSQ2=y-hJX3Rsh-rn=X9tcqv1VQ@mail.gmail.com","subject":"[PATCH v14 4/7] receive-pack: feed report options to post-receive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-05T14:41:28Z","receivedAt":"2020-05-05T14:41:50Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen commands are fed to the \"post-receive\" hook, report options will\nbe parsed and the real old-oid, new-oid, reference name will feed to\nthe \"post-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c                        | 29 ++++++++++++++++---\n t/t5411/test-0032-report-with-options.sh      | 14 ++++-----\n ...est-0033-report-with-options--porcelain.sh | 14 ++++-----\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 11 +++++--\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 11 +++++--\n t/t5411/test-0038-report-mixed-refs.sh        |  2 +-\n .../test-0039-report-mixed-refs--porcelain.sh |  2 +-\n 7 files changed, 57 insertions(+), 26 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 8b96a995b8..f57d997121 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -775,17 +775,38 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n {\n \tstruct receive_hook_feed_state *state = state_;\n \tstruct command *cmd = state->cmd;\n+\tstatic struct ref_push_report_options *options = NULL;\n \n \twhile (cmd &&\n \t       state->skip_broken && (cmd->report.error_message || cmd->did_not_exist))\n \t\tcmd = cmd->next;\n \tif (!cmd)\n \t\treturn -1; /* EOF */\n+\tif (!bufp)\n+\t\treturn 0; /* OK, can feed something. */\n \tstrbuf_reset(&state->buf);\n-\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n-\t\t    oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),\n-\t\t    cmd->ref_name);\n-\tstate->cmd = cmd->next;\n+\tif (!options)\n+\t\toptions = cmd->report.options;\n+\tif (options) {\n+\t\tstruct object_id *old_oid;\n+\t\tstruct object_id *new_oid;\n+\t\tchar *ref_name;\n+\n+\t\told_oid = options->old_oid ? options->old_oid : &cmd->old_oid;\n+\t\tnew_oid = options->new_oid ? options->new_oid : &cmd->new_oid;\n+\t\tref_name = options->ref_name ? options->ref_name : cmd->ref_name;\n+\t\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n+\t\t\t    oid_to_hex(old_oid), oid_to_hex(new_oid),\n+\t\t\t    ref_name);\n+\t\toptions = options->next;\n+\t\tif (!options)\n+\t\t\tstate->cmd = cmd->next;\n+\t} else {\n+\t\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n+\t\t\t    oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),\n+\t\t\t    cmd->ref_name);\n+\t\tstate->cmd = cmd->next;\n+\t}\n \tif (bufp) {\n \t\t*bufp = state->buf.buf;\n \t\t*sizep = state->buf.len;\ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nindex 3742ed3c3d..b77b78c49f 100644\n--- a/t/t5411/test-0032-report-with-options.sh\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -54,7 +54,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL)\" '\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n@@ -87,7 +87,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n@@ -121,7 +121,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL)\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t <OID-B>..<OID-A> HEAD -> refs/pull/123/head\n \tEOF\n@@ -153,7 +153,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL)\" '\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t <OID-B>..<OID-A> HEAD -> refs/for/master/topic\n \tEOF\n@@ -187,7 +187,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL)\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \tEOF\n@@ -237,9 +237,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n \tremote: proc-receive> option forced-update\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/pull/123/head\n \t * [new reference] HEAD -> refs/for/a/b/c/topic\ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nindex 929c46ccd8..b817d645f1 100644\n--- a/t/t5411/test-0033-report-with-options--porcelain.sh\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -55,7 +55,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL/porcelain)\"\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n@@ -90,7 +90,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n@@ -125,7 +125,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL/\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t     HEAD:refs/pull/123/head    <OID-B>..<OID-A>\n \tDone\n@@ -158,7 +158,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL/porcelain)\"\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t     HEAD:refs/for/master/topic    <OID-B>..<OID-A>\n \tDone\n@@ -193,7 +193,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL/\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \tDone\n@@ -245,9 +245,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porc\n \tremote: proc-receive> option forced-update\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/pull/123/head    [new reference]\n \t*    HEAD:refs/for/a/b/c/topic    [new reference]\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex 64662afdc6..27d58edd14 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -41,7 +41,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \t * [new reference] HEAD -> refs/changes/24/124/1\n@@ -101,7 +103,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/changes/24/124/1\n \t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n@@ -149,7 +153,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/changes/23/123/1\n \t <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2\ndiff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\nindex a371c8d610..2519c1acd8 100644\n--- a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n@@ -41,7 +41,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \t*    HEAD:refs/changes/24/124/1    [new reference]\n@@ -102,7 +104,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/changes/24/124/1    [new reference]\n \t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n@@ -151,7 +155,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porc\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/changes/23/123/1    [new reference]\n \t     HEAD:refs/changes/24/124/2    <OID-A>..<OID-B>\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nindex 9b060b2f84..b3fcd2283e 100644\n--- a/t/t5411/test-0038-report-mixed-refs.sh\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -45,7 +45,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t <OID-A>..<OID-B> <COMMIT-B> -> master\n \t * [new branch] HEAD -> bar\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nindex 537d4046b9..693b55549f 100644\n--- a/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -45,7 +45,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcel\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n \t*    HEAD:refs/heads/bar    [new branch]\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"397094","messageId":"20200505144131.30048-8-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"CANYiYbFCG1_hdy1kA06wjrvGSQ2=y-hJX3Rsh-rn=X9tcqv1VQ@mail.gmail.com","subject":"[PATCH v14 7/7] doc: add documentation for the proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-05T14:41:31Z","receivedAt":"2020-05-05T14:41:51Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd documentation for the new \"proc-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/githooks.txt | 62 ++++++++++++++++++++++++++++++++++++++\n 1 file changed, 62 insertions(+)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 81f2a87e88..d66154f606 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -333,6 +333,68 @@ The default 'update' hook, when enabled--and with\n `hooks.allowunannotated` config option unset or set to false--prevents\n unannotated tags to be pushed.\n \n+[[proc-receive]]\n+proc-receive\n+~~~~~~~~~~~~\n+\n+This hook is invoked by linkgit:git-receive-pack[1].  If the server has\n+set the multi-valued config variable `receive.procReceiveRefs`, and the\n+commands sent to 'receive-pack' have matching reference names, these\n+commands will be executed by this hook, instead of by the internal\n+`execute_commands()` function.  This hook is responsible for updating\n+the relevant references and reporting the results back to 'receive-pack'.\n+\n+This hook executes once for the receive operation.  It takes no\n+arguments, but uses a pkt-line format protocol to communicate with\n+'receive-pack' to read commands, push-options and send results.  In the\n+following example for the protocol, the letter 'S' stands for\n+'receive-pack' and the letter 'H' stands for this hook.\n+\n+    # Version and features negotiation.\n+    S: PKT-LINE(version=1\\0push-options atomic...)\n+    S: flush-pkt\n+    H: PKT-LINE(version=1\\0push-options...)\n+    H: flush-pkt\n+\n+    # Send commands from server to the hook.\n+    S: PKT-LINE(<old-oid> <new-oid> <ref>)\n+    S: ... ...\n+    S: flush-pkt\n+    # Send push-options only if the 'push-options' feature is enabled.\n+    S: PKT-LINE(push-option)\n+    S: ... ...\n+    S: flush-pkt\n+\n+    # Receive result from the hook.\n+    # OK, run this command successfully.\n+    H: PKT-LINE(ok <ref>)\n+    # NO, I reject it.\n+    H: PKT-LINE(ng <ref> <reason>)\n+    # Fall through, let 'receive-pack' to execute it.\n+    H: PKT-LINE(ok <ref>)\n+    H: PKT-LINE(option fall-through)\n+    # OK, but has an alternate reference.  The alternate reference name\n+    # and other status can be given in options\n+    H: PKT-LINE(ok <ref>)\n+    H: PKT-LINE(option refname <refname>)\n+    H: PKT-LINE(option old-oid <old-oid>)\n+    H: PKT-LINE(option new-oid <new-oid>)\n+    H: PKT-LINE(option forced-update)\n+    H: ... ...\n+    H: flush-pkt\n+\n+Each command for the 'proc-receive' hook may point to a pseudo-reference\n+and always has a zero-old as its old-oid, while the 'proc-receive' hook\n+may update an alternate reference and the alternate reference may exist\n+already with a non-zero old-oid.  For this case, this hook will use\n+\"option\" directives to report extended attributes for the reference given\n+by the above \"ok/ng\" directive.\n+\n+The report of the commands of this hook should have the same order as\n+the input.  The exit status of the 'proc-receive' hook only determines\n+the success or failure of the group of commands sent to it, unless\n+atomic push is in use.\n+\n [[post-receive]]\n post-receive\n ~~~~~~~~~~~~\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"397095","messageId":"20200505144131.30048-7-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"CANYiYbFCG1_hdy1kA06wjrvGSQ2=y-hJX3Rsh-rn=X9tcqv1VQ@mail.gmail.com","subject":"[PATCH v14 6/7] receive-pack: new config receive.procReceiveRefs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-05T14:41:30Z","receivedAt":"2020-05-05T14:41:52Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd a new multi-valued config variable \"receive.procReceiveRefs\"\nfor `receive-pack` command, like the follows:\n\n    git config --system --add receive.procReceiveRefs refs/for\n    git config --system --add receive.procReceiveRefs refs/drafts\n\nIf the specific prefix strings match the reference names of the commands\nwhich are sent from git client to `receive-pack`, these commands will be\nexecuted by an external hook (named \"proc-receive\"), instead of the\ninternal `execute_commands` function.\n\nFor example, if it is set to \"refs/for\", pushing to a reference such as\n\"refs/for/master\" will not create or update reference \"refs/for/master\",\nbut may create or update a pull request directly by running the hook\n\"proc-receive\".\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/config/receive.txt              |  14 +++\n builtin/receive-pack.c                        |  43 +++++--\n t/t5411/test-0010-proc-receive-settings.sh    |   7 ++\n t/t5411/test-0040-process-all-refs.sh         | 113 +++++++++++++++++\n .../test-0041-process-all-refs--porcelain.sh  | 114 ++++++++++++++++++\n 5 files changed, 284 insertions(+), 7 deletions(-)\n create mode 100644 t/t5411/test-0010-proc-receive-settings.sh\n create mode 100644 t/t5411/test-0040-process-all-refs.sh\n create mode 100644 t/t5411/test-0041-process-all-refs--porcelain.sh\n\ndiff --git a/Documentation/config/receive.txt b/Documentation/config/receive.txt\nindex 65f78aac37..e7b967feeb 100644\n--- a/Documentation/config/receive.txt\n+++ b/Documentation/config/receive.txt\n@@ -114,6 +114,20 @@ receive.hideRefs::\n \tAn attempt to update or delete a hidden ref by `git push` is\n \trejected.\n \n+receive.procReceiveRefs::\n+\tThis is a multi-valued variable that defines reference prefixes\n+\tto match the commands in `receive-pack`.  Commands matching the\n+\tprefixes will be executed by an external hook \"proc-receive\",\n+\tinstead of the internal `execute_commands` function.  If this\n+\tvariable is not defined, the \"proc-receive\" hook will never be\n+\tused, and all commands will be executed by the internal\n+\t`execute_commands` function.\n+\n+\tFor example, if this variable is set to \"refs/for\", pushing to\n+\treference such as \"refs/for/master\" will not create or update a\n+\treference named \"refs/for/master\", but may create or update a\n+\tpull request directly by running the hook \"proc-receive\".\n+\n receive.updateServerInfo::\n \tIf set to true, git-receive-pack will run git-update-server-info\n \tafter receiving data from git-push and updating refs.\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex f57d997121..88926c8664 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -77,6 +77,7 @@ static struct object_id push_cert_oid;\n static struct signature_check sigcheck;\n static const char *push_cert_nonce;\n static const char *cert_nonce_seed;\n+static struct string_list proc_receive_refs;\n \n static const char *NONCE_UNSOLICITED = \"UNSOLICITED\";\n static const char *NONCE_BAD = \"BAD\";\n@@ -229,6 +230,20 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (strcmp(var, \"receive.procreceiverefs\") == 0) {\n+\t\tchar *prefix;\n+\t\tint len;\n+\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tprefix = xstrdup(value);\n+\t\tlen = strlen(prefix);\n+\t\twhile (len && prefix[len - 1] == '/')\n+\t\t\tprefix[--len] = '\\0';\n+\t\tstring_list_append(&proc_receive_refs, prefix);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -1810,15 +1825,26 @@ static void execute_commands(struct command *commands,\n \t * Try to find commands that have special prefix in their reference names,\n \t * and mark them to run an external \"proc-receive\" hook later.\n \t */\n-\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n-\t\t\tcontinue;\n+\tif (proc_receive_refs.nr > 0) {\n+\t\tstruct strbuf refname_full = STRBUF_INIT;\n+\t\tsize_t prefix_len;\n+\n+\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n+\t\tprefix_len = refname_full.len;\n \n-\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n-\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n-\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n-\t\t\trun_proc_receive = 1;\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tif (!should_process_cmd(cmd))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_setlen(&refname_full, prefix_len);\n+\t\t\tstrbuf_addstr(&refname_full, cmd->ref_name);\n+\t\t\tif (ref_matches(&proc_receive_refs, cmd->ref_name, refname_full.buf)) {\n+\t\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\t\trun_proc_receive = 1;\n+\t\t\t}\n \t\t}\n+\n+\t\tstrbuf_release(&refname_full);\n \t}\n \n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n@@ -2317,6 +2343,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n+\tstring_list_init(&proc_receive_refs, 0);\n+\n \tpacket_trace_identity(\"receive-pack\");\n \n \targc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);\n@@ -2434,5 +2462,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \toid_array_clear(&shallow);\n \toid_array_clear(&ref);\n \tfree((void *)push_cert_nonce);\n+\tstring_list_clear(&proc_receive_refs, 0);\n \treturn 0;\n }\ndiff --git a/t/t5411/test-0010-proc-receive-settings.sh b/t/t5411/test-0010-proc-receive-settings.sh\nnew file mode 100644\nindex 0000000000..a36809927b\n--- /dev/null\n+++ b/t/t5411/test-0010-proc-receive-settings.sh\n@@ -0,0 +1,7 @@\n+test_expect_success \"add two receive.procReceiveRefs settings\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit config --add receive.procReceiveRefs refs/for &&\n+\t\tgit config --add receive.procReceiveRefs refs/review/\n+\t)\n+'\ndiff --git a/t/t5411/test-0040-process-all-refs.sh b/t/t5411/test-0040-process-all-refs.sh\nnew file mode 100644\nindex 0000000000..b07c999f53\n--- /dev/null\n+++ b/t/t5411/test-0040-process-all-refs.sh\n@@ -0,0 +1,113 @@\n+test_expect_success \"config receive.procReceiveRefs = refs ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config --unset-all receive.procReceiveRefs &&\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup upstream branches ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $B &&\n+\t\tgit update-ref refs/heads/foo $A &&\n+\t\tgit update-ref refs/heads/bar $A &&\n+\t\tgit update-ref refs/heads/baz $A\n+\t)\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/foo\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/bar\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"proc-receive: process all refs ($PROTOCOL)\" '\n+\tgit -C workbench push -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/foo\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/bar\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> bar\n+\t - [deleted] foo\n+\t + <OID-B>...<OID-A> HEAD -> master (forced update)\n+\t <OID-A>..<OID-B> HEAD -> refs/pull/123/head\n+\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             bar(A)  baz(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+'\ndiff --git a/t/t5411/test-0041-process-all-refs--porcelain.sh b/t/t5411/test-0041-process-all-refs--porcelain.sh\nnew file mode 100644\nindex 0000000000..7092db0a06\n--- /dev/null\n+++ b/t/t5411/test-0041-process-all-refs--porcelain.sh\n@@ -0,0 +1,114 @@\n+test_expect_success \"config receive.procReceiveRefs = refs ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config --unset-all receive.procReceiveRefs &&\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup upstream branches ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $B &&\n+\t\tgit update-ref refs/heads/foo $A &&\n+\t\tgit update-ref refs/heads/bar $A &&\n+\t\tgit update-ref refs/heads/baz $A\n+\t)\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/foo\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/bar\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"proc-receive: process all refs ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/foo\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/bar\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/bar    <OID-A>..<OID-B>\n+\t-    :refs/heads/foo    [deleted]\n+\t+    HEAD:refs/heads/master    <OID-B>...<OID-A> (forced update)\n+\t     HEAD:refs/pull/123/head    <OID-A>..<OID-B>\n+\t+    HEAD:refs/pull/124/head    <OID-B>...<OID-A> (forced update)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             bar(A)  baz(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+'\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"397098","messageId":"20200505144131.30048-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"CANYiYbFCG1_hdy1kA06wjrvGSQ2=y-hJX3Rsh-rn=X9tcqv1VQ@mail.gmail.com","subject":"[PATCH v14 2/7] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-05T14:41:26Z","receivedAt":"2020-05-05T14:41:53Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nGit calls an internal `execute_commands` function to handle commands\nsent from client to `git-receive-pack`.  Regardless of what references\nthe user pushes, git creates or updates the corresponding references if\nthe user has write-permission.  A contributor who has no\nwrite-permission, cannot push to the repository directly.  So, the\ncontributor has to write commits to an alternate location, and sends\npull request by emails or by other ways.  We call this workflow as a\ndistributed workflow.\n\nIt would be more convenient to work in a centralized workflow like what\nGerrit provided for some cases.  For example, a read-only user who\ncannot push to a branch directly can run the following `git push`\ncommand to push commits to a pseudo reference (has a prefix \"refs/for/\",\nnot \"refs/heads/\") to create a code review.\n\n    git push origin \\\n        HEAD:refs/for/<branch-name>/<session>\n\nThe `<branch-name>` in the above example can be as simple as \"master\",\nor a more complicated branch name like \"foo/bar\".  The `<session>` in\nthe above example command can be the local branch name of the client\nside, such as \"my/topic\".\n\nWe cannot implement a centralized workflow elegantly by using\n\"pre-receive\" + \"post-receive\", because Git will call the internal\nfunction \"execute_commands\" to create references (even the special\npseudo reference) between these two hooks.  Even though we can delete\nthe temporarily created pseudo reference via the \"post-receive\" hook,\nhaving a temporary reference is not safe for concurrent pushes.\n\nSo, add a filter and a new handler to support this kind of workflow.\nThe filter will check the prefix of the reference name, and if the\ncommand has a special reference name, the filter will turn a specific\nfield (`run_proc_receive`) on for the command.  Commands with this filed\nturned on will be executed by a new handler (an hook named\n\"proc-receive\") instead of the internal `execute_commands` function.\nWe can use this \"proc-receive\" command to create pull requests or send\nemails for code review.\n\nSuggested by Junio, this \"proc-receive\" hook reads the commands,\npush-options (optional), and send result using a protocol in pkt-line\nformat.  In the following example, The letter \"S\" stands for\n\"receive-pack\" and letter \"H\" stands for the hook.\n\n    # Version and features negotiation.\n    S: PKT-LINE(version=1\\0push-options atomic...)\n    S: flush-pkt\n    H: PKT-LINE(version=1\\0push-options...)\n    H: flush-pkt\n\n    # Send commands from server to the hook.\n    S: PKT-LINE(<old-oid> <new-oid> <ref>)\n    S: ... ...\n    S: flush-pkt\n    # Send push-options only if the 'push-options' feature is enabled.\n    S: PKT-LINE(push-option)\n    S: ... ...\n    S: flush-pkt\n\n    # Receive result from the hook.\n    # OK, run this command successfully.\n    H: PKT-LINE(ok <ref>)\n    # NO, I reject it.\n    H: PKT-LINE(ng <ref> <reason>)\n    # Fall through, let 'receive-pack' to execute it.\n    H: PKT-LINE(ok <ref>)\n    H: PKT-LINE(option fall-through)\n    # OK, but has an alternate reference.  The alternate reference name\n    # and other status can be given in options\n    H: PKT-LINE(ok <ref>)\n    H: PKT-LINE(option refname <refname>)\n    H: PKT-LINE(option old-oid <old-oid>)\n    H: PKT-LINE(option new-oid <new-oid>)\n    H: PKT-LINE(option forced-update)\n    H: ... ...\n    H: flush-pkt\n\nAfter receiving a command, the hook will execute the command, and may\ncreate/update different reference.  For example, a command for a pseudo\nreference \"refs/for/master/topic\" may create/update different reference\nsuch as \"refs/pull/123/head\".  The alternate reference name and other\nstatus are given in option lines.\n\nThe list of commands returned from \"proc-receive\" will replace the\nrelevant commands that are sent from user to \"receive-pack\", and\n\"receive-pack\" will continue to run the \"execute_commands\" function and\nother routines.  Finally, the result of the execution of these commands\nwill be reported to end user.\n\nThe reporting function from \"receive-pack\" to \"send-pack\" will be\nextended in latter commit just like what the \"proc-receive\" hook reports\nto \"receive-pack\".\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Makefile                                      |   1 +\n builtin/receive-pack.c                        | 351 ++++++++++++++++--\n remote.h                                      |  14 +\n t/helper/test-proc-receive.c                  | 175 +++++++++\n t/helper/test-tool.c                          |   1 +\n t/helper/test-tool.h                          |   1 +\n t/t5411/test-0011-no-hook-error.sh            |  64 ++++\n t/t5411/test-0012-no-hook-error--porcelain.sh |  66 ++++\n t/t5411/test-0013-bad-protocol.sh             | 225 +++++++++++\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 160 ++++++++\n t/t5411/test-0020-report-ng.sh                |  67 ++++\n t/t5411/test-0021-report-ng--porcelain.sh     |  69 ++++\n t/t5411/test-0022-report-unexpect-ref.sh      |  45 +++\n ...est-0023-report-unexpect-ref--porcelain.sh |  46 +++\n t/t5411/test-0024-report-unknown-ref.sh       |  34 ++\n ...test-0025-report-unknown-ref--porcelain.sh |  35 ++\n t/t5411/test-0026-push-options.sh             |  79 ++++\n t/t5411/test-0027-push-options--porcelain.sh  |  82 ++++\n t/t5411/test-0030-report-ok.sh                |  35 ++\n t/t5411/test-0031-report-ok--porcelain.sh     |  36 ++\n t/t5411/test-0032-report-with-options.sh      | 256 +++++++++++++\n ...est-0033-report-with-options--porcelain.sh | 265 +++++++++++++\n t/t5411/test-0034-report-ft.sh                |  44 +++\n t/t5411/test-0035-report-ft--porcelain.sh     |  45 +++\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 159 ++++++++\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 162 ++++++++\n t/t5411/test-0038-report-mixed-refs.sh        |  79 ++++\n .../test-0039-report-mixed-refs--porcelain.sh |  81 ++++\n 28 files changed, 2645 insertions(+), 32 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100644 t/t5411/test-0011-no-hook-error.sh\n create mode 100644 t/t5411/test-0012-no-hook-error--porcelain.sh\n create mode 100644 t/t5411/test-0013-bad-protocol.sh\n create mode 100644 t/t5411/test-0014-bad-protocol--porcelain.sh\n create mode 100644 t/t5411/test-0020-report-ng.sh\n create mode 100644 t/t5411/test-0021-report-ng--porcelain.sh\n create mode 100644 t/t5411/test-0022-report-unexpect-ref.sh\n create mode 100644 t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n create mode 100644 t/t5411/test-0024-report-unknown-ref.sh\n create mode 100644 t/t5411/test-0025-report-unknown-ref--porcelain.sh\n create mode 100644 t/t5411/test-0026-push-options.sh\n create mode 100644 t/t5411/test-0027-push-options--porcelain.sh\n create mode 100644 t/t5411/test-0030-report-ok.sh\n create mode 100644 t/t5411/test-0031-report-ok--porcelain.sh\n create mode 100644 t/t5411/test-0032-report-with-options.sh\n create mode 100644 t/t5411/test-0033-report-with-options--porcelain.sh\n create mode 100644 t/t5411/test-0034-report-ft.sh\n create mode 100644 t/t5411/test-0035-report-ft--porcelain.sh\n create mode 100644 t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n create mode 100644 t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n create mode 100644 t/t5411/test-0038-report-mixed-refs.sh\n create mode 100644 t/t5411/test-0039-report-mixed-refs--porcelain.sh\n\ndiff --git a/Makefile b/Makefile\nindex 3d3a39fc19..607aeabeee 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -722,6 +722,7 @@ TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\n+TEST_BUILTINS_OBJS += test-proc-receive.o\n TEST_BUILTINS_OBJS += test-progress.o\n TEST_BUILTINS_OBJS += test-reach.o\n TEST_BUILTINS_OBJS += test-read-cache.o\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex a00f91c1a0..7575eec320 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -308,11 +308,14 @@ static void write_head_info(void)\n \tpacket_flush(1);\n }\n \n+#define RUN_PROC_RECEIVE_SCHEDULED\t1\n+#define RUN_PROC_RECEIVE_RETURNED\t2\n struct command {\n \tstruct command *next;\n-\tconst char *error_string;\n+\tstruct ref_push_report report;\n \tunsigned int skip_update:1,\n-\t\t     did_not_exist:1;\n+\t\t     did_not_exist:1,\n+\t\t     run_proc_receive:2;\n \tint index;\n \tstruct object_id old_oid;\n \tstruct object_id new_oid;\n@@ -773,7 +776,7 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n \tstruct command *cmd = state->cmd;\n \n \twhile (cmd &&\n-\t       state->skip_broken && (cmd->error_string || cmd->did_not_exist))\n+\t       state->skip_broken && (cmd->report.error_message || cmd->did_not_exist))\n \t\tcmd = cmd->next;\n \tif (!cmd)\n \t\treturn -1; /* EOF */\n@@ -838,6 +841,268 @@ static int run_update_hook(struct command *cmd)\n \treturn finish_command(&proc);\n }\n \n+static struct command *find_command_by_refname(const struct command *list,\n+\t\t\t\t\t       const char *refname)\n+{\n+\tfor (; list; list = list->next)\n+\t\tif (!strcmp(list->ref_name, refname))\n+\t\t\treturn (struct command *)list;\n+\treturn NULL;\n+}\n+\n+static int read_proc_receive_report(struct packet_reader *reader,\n+\t\t\t\t    struct command *commands,\n+\t\t\t\t    struct strbuf *errmsg)\n+{\n+\tstruct command *cmd;\n+\tstruct command *hint = NULL;\n+\tint code = 0;\n+\tint new_options = 1;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *head;\n+\t\tconst char *refname;\n+\t\tchar *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\thead = reader->line;\n+\t\tp = strchr(head, ' ');\n+\t\tif (!p) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported incomplete status line: '%s'\\n\", head);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\t*p++ = '\\0';\n+\t\tif (!strcmp(head, \"option\")) {\n+\t\t\tstruct ref_push_report_options *options;\n+\t\t\tconst char *key, *val;\n+\n+\t\t\tif (!hint) {\n+\t\t\t       if (new_options) {\n+\t\t\t\t\tstrbuf_addstr(errmsg, \"proc-receive reported 'option' without a matching 'ok/ng' directive\\n\");\n+\t\t\t\t\tnew_options = 0;\n+\t\t\t\t}\n+\t\t\t\tcode = -1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\toptions = hint->report.options;\n+\t\t\twhile (options && options->next)\n+\t\t\t\toptions = options->next;\n+\t\t\tif (new_options) {\n+\t\t\t\tif (!options) {\n+\t\t\t\t\thint->report.options = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\t\toptions = hint->report.options;\n+\t\t\t\t} else {\n+\t\t\t\t\toptions->next = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\t\toptions = options->next;\n+\t\t\t\t}\n+\t\t\t\tnew_options = 0;\n+\t\t\t}\n+\t\t\tassert(options);\n+\t\t\tkey = p;\n+\t\t\tp = strchr(key, ' ');\n+\t\t\tif (p)\n+\t\t\t\t*p++ = '\\0';\n+\t\t\tval = p;\n+\t\t\tif (!strcmp(key, \"refname\"))\n+\t\t\t\toptions->ref_name = xstrdup_or_null(val);\n+\t\t\telse if (!strcmp(key, \"old-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &old_oid, &val))\n+\t\t\t\toptions->old_oid = oiddup(&old_oid);\n+\t\t\telse if (!strcmp(key, \"new-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &new_oid, &val))\n+\t\t\t\toptions->new_oid = oiddup(&new_oid);\n+\t\t\telse if (!strcmp(key, \"forced-update\"))\n+\t\t\t\toptions->forced_update = 1;\n+\t\t\telse if (!strcmp(key, \"fall-through\"))\n+\t\t\t\t/* Fall through, let 'receive-pack' to execute it. */\n+\t\t\t\thint->run_proc_receive = 0;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\trefname = p;\n+\t\tp = strchr(refname, ' ');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n+\t\tif (strcmp(head, \"ok\") && strcmp(head, \"ng\")) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported bad status '%s' on ref '%s'\\n\",\n+\t\t\t\t    head, refname);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/* first try searching at our hint, falling back to all refs */\n+\t\tif (hint)\n+\t\t\thint = find_command_by_refname(hint, refname);\n+\t\tif (!hint)\n+\t\t\thint = find_command_by_refname(commands, refname);\n+\t\tif (!hint) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unknown ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!hint->run_proc_receive) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unexpected ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(head, \"ng\")) {\n+\t\t\tif (p)\n+\t\t\t\thint->report.error_message = xstrdup(p);\n+\t\t\telse\n+\t\t\t\thint->report.error_message = \"failed\";\n+\t\t\tcode = -1;\n+\t\t}\n+\t\tif (hint->run_proc_receive)\n+\t\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n+\t\tnew_options = 1;\n+\t}\n+\n+\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\tif (cmd->run_proc_receive && !cmd->report.error_message &&\n+\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {\n+\t\t    cmd->report.error_message = \"proc-receive failed to report status\";\n+\t\t    code = -1;\n+\t\t}\n+\treturn code;\n+}\n+\n+static int run_proc_receive_hook(struct command *commands,\n+\t\t\t\t const struct string_list *push_options)\n+{\n+\tstruct child_process proc = CHILD_PROCESS_INIT;\n+\tstruct async muxer;\n+\tstruct command *cmd;\n+\tconst char *argv[2];\n+\tstruct packet_reader reader;\n+\tstruct strbuf cap = STRBUF_INIT;\n+\tstruct strbuf errmsg = STRBUF_INIT;\n+\tint hook_use_push_options = 0;\n+\tint version = 0;\n+\tint code;\n+\n+\targv[0] = find_hook(\"proc-receive\");\n+\tif (!argv[0]) {\n+\t\trp_error(\"cannot find hook 'proc-receive'\");\n+\t\treturn -1;\n+\t}\n+\targv[1] = NULL;\n+\n+\tproc.argv = argv;\n+\tproc.in = -1;\n+\tproc.out = -1;\n+\tproc.trace2_hook_name = \"proc-receive\";\n+\n+\tif (use_sideband) {\n+\t\tmemset(&muxer, 0, sizeof(muxer));\n+\t\tmuxer.proc = copy_to_sideband;\n+\t\tmuxer.in = -1;\n+\t\tcode = start_async(&muxer);\n+\t\tif (code)\n+\t\t\treturn code;\n+\t\tproc.err = muxer.in;\n+\t} else {\n+\t\tproc.err = 0;\n+\t}\n+\n+\tcode = start_command(&proc);\n+\tif (code) {\n+\t\tif (use_sideband)\n+\t\t\tfinish_async(&muxer);\n+\t\treturn code;\n+\t}\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\n+\t/* Version negotiaton */\n+\tpacket_reader_init(&reader, proc.out, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_GENTLE_ON_EOF);\n+\tif (use_atomic)\n+\t\tstrbuf_addstr(&cap, \" atomic\");\n+\tif (use_push_options)\n+\t\tstrbuf_addstr(&cap, \" push-options\");\n+\tif (cap.len) {\n+\t\tpacket_write_fmt(proc.in, \"version=1%c%s\\n\", '\\0', cap.buf + 1);\n+\t\tstrbuf_release(&cap);\n+\t} else {\n+\t\tpacket_write_fmt(proc.in, \"version=1\\n\");\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader.pktlen > 8 && starts_with(reader.line, \"version=\")) {\n+\t\t\tversion = atoi(reader.line + 8);\n+\t\t\tlinelen = strlen(reader.line);\n+\t\t\tif (linelen < reader.pktlen) {\n+\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\thook_use_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (version != 1) {\n+\t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n+\t\t\t    version);\n+\t\tcode = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\t/* Send commands */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->report.error_message)\n+\t\t\tcontinue;\n+\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n+\t\t\t\t oid_to_hex(&cmd->old_oid),\n+\t\t\t\t oid_to_hex(&cmd->new_oid),\n+\t\t\t\t cmd->ref_name);\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\t/* Send push options */\n+\tif (hook_use_push_options) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tfor_each_string_list_item(item, push_options)\n+\t\t\tpacket_write_fmt(proc.in, \"%s\", item->string);\n+\t\tpacket_flush(proc.in);\n+\t}\n+\n+\t/* Read result from proc-receive */\n+\tcode = read_proc_receive_report(&reader, commands, &errmsg);\n+\n+cleanup:\n+\tclose(proc.in);\n+\tclose(proc.out);\n+\tif (use_sideband)\n+\t\tfinish_async(&muxer);\n+\tif (finish_command(&proc))\n+\t\tcode = -1;\n+\tif (errmsg.len >0) {\n+\t\tchar *p = errmsg.buf;\n+\n+\t\tp += errmsg.len - 1;\n+\t\tif (*p == '\\n')\n+\t\t\t*p = '\\0';\n+\t\trp_error(\"%s\", errmsg.buf);\n+\t\tstrbuf_release(&errmsg);\n+\t}\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn code;\n+}\n+\n static char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n@@ -1201,7 +1466,7 @@ static void run_update_post_hook(struct command *commands)\n \t\treturn;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (cmd->error_string || cmd->did_not_exist)\n+\t\tif (cmd->report.error_message || cmd->did_not_exist)\n \t\t\tcontinue;\n \t\tif (!proc.args.argc)\n \t\t\targv_array_push(&proc.args, hook);\n@@ -1235,7 +1500,7 @@ static void check_aliased_update_internal(struct command *cmd,\n \tif (!dst_name) {\n \t\trp_error(\"refusing update to broken symref '%s'\", cmd->ref_name);\n \t\tcmd->skip_update = 1;\n-\t\tcmd->error_string = \"broken symref\";\n+\t\tcmd->report.error_message = \"broken symref\";\n \t\treturn;\n \t}\n \tdst_name = strip_namespace(dst_name);\n@@ -1262,7 +1527,7 @@ static void check_aliased_update_internal(struct command *cmd,\n \t\t find_unique_abbrev(&dst_cmd->old_oid, DEFAULT_ABBREV),\n \t\t find_unique_abbrev(&dst_cmd->new_oid, DEFAULT_ABBREV));\n \n-\tcmd->error_string = dst_cmd->error_string =\n+\tcmd->report.error_message = dst_cmd->report.error_message =\n \t\t\"inconsistent aliased update\";\n }\n \n@@ -1291,7 +1556,7 @@ static void check_aliased_updates(struct command *commands)\n \tstring_list_sort(&ref_list);\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!cmd->error_string)\n+\t\tif (!cmd->report.error_message)\n \t\t\tcheck_aliased_update(cmd, &ref_list);\n \t}\n \n@@ -1328,7 +1593,7 @@ static void set_connectivity_errors(struct command *commands,\n \t\t\t\t     &opt))\n \t\t\tcontinue;\n \n-\t\tcmd->error_string = \"missing necessary objects\";\n+\t\tcmd->report.error_message = \"missing necessary objects\";\n \t}\n }\n \n@@ -1367,7 +1632,7 @@ static void reject_updates_to_hidden(struct command *commands)\n \tprefix_len = refname_full.len;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (cmd->error_string)\n+\t\tif (cmd->report.error_message)\n \t\t\tcontinue;\n \n \t\tstrbuf_setlen(&refname_full, prefix_len);\n@@ -1376,9 +1641,9 @@ static void reject_updates_to_hidden(struct command *commands)\n \t\tif (!ref_is_hidden(cmd->ref_name, refname_full.buf))\n \t\t\tcontinue;\n \t\tif (is_null_oid(&cmd->new_oid))\n-\t\t\tcmd->error_string = \"deny deleting a hidden ref\";\n+\t\t\tcmd->report.error_message = \"deny deleting a hidden ref\";\n \t\telse\n-\t\t\tcmd->error_string = \"deny updating a hidden ref\";\n+\t\t\tcmd->report.error_message = \"deny updating a hidden ref\";\n \t}\n \n \tstrbuf_release(&refname_full);\n@@ -1386,7 +1651,7 @@ static void reject_updates_to_hidden(struct command *commands)\n \n static int should_process_cmd(struct command *cmd)\n {\n-\treturn !cmd->error_string && !cmd->skip_update;\n+\treturn !cmd->report.error_message && !cmd->skip_update;\n }\n \n static void warn_if_skipped_connectivity_check(struct command *commands,\n@@ -1413,24 +1678,24 @@ static void execute_commands_non_atomic(struct command *commands,\n \tstruct strbuf err = STRBUF_INIT;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\ttransaction = ref_transaction_begin(&err);\n \t\tif (!transaction) {\n \t\t\trp_error(\"%s\", err.buf);\n \t\t\tstrbuf_reset(&err);\n-\t\t\tcmd->error_string = \"transaction failed to start\";\n+\t\t\tcmd->report.error_message = \"transaction failed to start\";\n \t\t\tcontinue;\n \t\t}\n \n-\t\tcmd->error_string = update(cmd, si);\n+\t\tcmd->report.error_message = update(cmd, si);\n \n-\t\tif (!cmd->error_string\n+\t\tif (!cmd->report.error_message\n \t\t    && ref_transaction_commit(transaction, &err)) {\n \t\t\trp_error(\"%s\", err.buf);\n \t\t\tstrbuf_reset(&err);\n-\t\t\tcmd->error_string = \"failed to update ref\";\n+\t\t\tcmd->report.error_message = \"failed to update ref\";\n \t\t}\n \t\tref_transaction_free(transaction);\n \t}\n@@ -1453,12 +1718,12 @@ static void execute_commands_atomic(struct command *commands,\n \t}\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n-\t\tcmd->error_string = update(cmd, si);\n+\t\tcmd->report.error_message = update(cmd, si);\n \n-\t\tif (cmd->error_string)\n+\t\tif (cmd->report.error_message)\n \t\t\tgoto failure;\n \t}\n \n@@ -1471,8 +1736,8 @@ static void execute_commands_atomic(struct command *commands,\n \n failure:\n \tfor (cmd = commands; cmd; cmd = cmd->next)\n-\t\tif (!cmd->error_string)\n-\t\t\tcmd->error_string = reported_error;\n+\t\tif (!cmd->report.error_message)\n+\t\t\tcmd->report.error_message = reported_error;\n \n cleanup:\n \tref_transaction_free(transaction);\n@@ -1489,10 +1754,11 @@ static void execute_commands(struct command *commands,\n \tstruct iterate_data data;\n \tstruct async muxer;\n \tint err_fd = 0;\n+\tint run_proc_receive = 0;\n \n \tif (unpacker_error) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n-\t\t\tcmd->error_string = \"unpacker error\";\n+\t\t\tcmd->report.error_message = \"unpacker error\";\n \t\treturn;\n \t}\n \n@@ -1518,10 +1784,25 @@ static void execute_commands(struct command *commands,\n \n \treject_updates_to_hidden(commands);\n \n+\t/*\n+\t * Try to find commands that have special prefix in their reference names,\n+\t * and mark them to run an external \"proc-receive\" hook later.\n+\t */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!should_process_cmd(cmd))\n+\t\t\tcontinue;\n+\n+\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n+\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n+\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\trun_proc_receive = 1;\n+\t\t}\n+\t}\n+\n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\t\tif (!cmd->error_string)\n-\t\t\t\tcmd->error_string = \"pre-receive hook declined\";\n+\t\t\tif (!cmd->report.error_message)\n+\t\t\t\tcmd->report.error_message = \"pre-receive hook declined\";\n \t\t}\n \t\treturn;\n \t}\n@@ -1532,8 +1813,8 @@ static void execute_commands(struct command *commands,\n \t */\n \tif (tmp_objdir_migrate(tmp_objdir) < 0) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\t\tif (!cmd->error_string)\n-\t\t\t\tcmd->error_string = \"unable to migrate objects to permanent storage\";\n+\t\t\tif (!cmd->report.error_message)\n+\t\t\t\tcmd->report.error_message = \"unable to migrate objects to permanent storage\";\n \t\t}\n \t\treturn;\n \t}\n@@ -1544,6 +1825,12 @@ static void execute_commands(struct command *commands,\n \tfree(head_name_to_free);\n \thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, NULL, NULL);\n \n+\tif (run_proc_receive &&\n+\t    run_proc_receive_hook(commands, push_options))\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tif (!cmd->report.error_message  && (cmd->run_proc_receive || use_atomic))\n+\t\t\t\tcmd->report.error_message = \"fail to run proc-receive hook\";\n+\n \tif (use_atomic)\n \t\texecute_commands_atomic(commands, si);\n \telse\n@@ -1905,7 +2192,7 @@ static void update_shallow_info(struct command *commands,\n \t\tif (is_null_oid(&cmd->new_oid))\n \t\t\tcontinue;\n \t\tif (ref_status[cmd->index]) {\n-\t\t\tcmd->error_string = \"shallow update not allowed\";\n+\t\t\tcmd->report.error_message = \"shallow update not allowed\";\n \t\t\tcmd->skip_update = 1;\n \t\t}\n \t}\n@@ -1920,12 +2207,12 @@ static void report(struct command *commands, const char *unpack_status)\n \tpacket_buf_write(&buf, \"unpack %s\\n\",\n \t\t\t unpack_status ? unpack_status : \"ok\");\n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!cmd->error_string)\n+\t\tif (!cmd->report.error_message)\n \t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n-\t\t\t\t\t cmd->ref_name);\n+\t\t\t\t\t\t cmd->ref_name);\n \t\telse\n \t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n-\t\t\t\t\t cmd->ref_name, cmd->error_string);\n+\t\t\t\t\t cmd->ref_name, cmd->report.error_message);\n \t}\n \tpacket_buf_flush(&buf);\n \n@@ -2029,7 +2316,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tif (!check_cert_push_options(&push_options)) {\n \t\t\tstruct command *cmd;\n \t\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n-\t\t\t\tcmd->error_string = \"inconsistent push options\";\n+\t\t\t\tcmd->report.error_message = \"inconsistent push options\";\n \t\t}\n \n \t\tprepare_shallow_info(&si, &shallow);\ndiff --git a/remote.h b/remote.h\nindex 11d8719b58..2ed2a16e16 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -93,6 +93,20 @@ int for_each_remote(each_remote_fn fn, void *priv);\n \n int remote_has_url(struct remote *remote, const char *url);\n \n+struct ref_push_report_options {\n+\tchar *ref_name;\n+\tstruct object_id *old_oid;\n+\tstruct object_id *new_oid;\n+\tunsigned int forced_update:1;\n+\tstruct ref_push_report_options *next;\n+};\n+\n+struct ref_push_report {\n+\tconst char *message;\n+\tconst char *error_message;\n+\tstruct ref_push_report_options *options;\n+};\n+\n struct ref {\n \tstruct ref *next;\n \tstruct object_id old_oid;\ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nnew file mode 100644\nindex 0000000000..6ace1863b0\n--- /dev/null\n+++ b/t/helper/test-proc-receive.c\n@@ -0,0 +1,175 @@\n+#include \"cache.h\"\n+#include \"connect.h\"\n+#include \"parse-options.h\"\n+#include \"pkt-line.h\"\n+#include \"sigchain.h\"\n+#include \"string-list.h\"\n+#include \"test-tool.h\"\n+\n+static const char *proc_receive_usage[] = {\n+\t\"test-tool proc-receive [<options>...]\",\n+\tNULL\n+};\n+\n+static int die_version = 0;\n+static int die_readline = 0;\n+static int no_push_options = 0;\n+static int use_atomic = 0;\n+static int use_push_options = 0;\n+static int verbose = 0;\n+static int version = 1;\n+static struct string_list returns = STRING_LIST_INIT_NODUP;\n+\n+struct command {\n+\tstruct command *next;\n+\tconst char *error_string;\n+\tunsigned int skip_update:1,\n+\t\t     did_not_exist:1;\n+\tint index;\n+\tstruct object_id old_oid;\n+\tstruct object_id new_oid;\n+\tchar ref_name[FLEX_ARRAY]; /* more */\n+};\n+\n+static void proc_receive_verison(struct packet_reader *reader) {\n+\tint server_version = 0;\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader->pktlen > 8 && starts_with(reader->line, \"version=\")) {\n+\t\t\tserver_version = atoi(reader->line+8);\n+\t\t\tlinelen = strlen(reader->line);\n+\t\t\tif (linelen < reader->pktlen) {\n+\t\t\t\tconst char *feature_list = reader->line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"atomic\"))\n+\t\t\t\t\tuse_atomic= 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tuse_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (server_version != 1 || die_version)\n+\t\tdie(\"bad protocol version: %d\", server_version);\n+\n+\tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n+\t\t\t version, '\\0',\n+\t\t\t use_push_options && !no_push_options ? \"push-options\": \"\");\n+\tpacket_flush(1);\n+}\n+\n+static void proc_receive_read_commands(struct packet_reader *reader,\n+\t\t\t\t       struct command **commands)\n+{\n+\tstruct command **tail = commands;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tstruct command *cmd;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    die_readline)\n+\t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n+\t\t\t    reader->line);\n+\t\trefname = p;\n+\t\tFLEX_ALLOC_STR(cmd, ref_name, refname);\n+\t\toidcpy(&cmd->old_oid, &old_oid);\n+\t\toidcpy(&cmd->new_oid, &new_oid);\n+\n+\t\t*tail = cmd;\n+\t\ttail = &cmd->next;\n+\t}\n+}\n+\n+static void proc_receive_read_push_options(struct packet_reader *reader,\n+\t\t\t\t\t   struct string_list *options)\n+{\n+\n+\tif (no_push_options || !use_push_options)\n+\t       return;\n+\n+\twhile (1) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tstring_list_append(options, reader->line);\n+\t}\n+}\n+\n+int cmd__proc_receive(int argc, const char **argv)\n+{\n+\tstruct packet_reader reader;\n+\tstruct command *commands = NULL;\n+\tstruct string_list push_options = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"no-push-options\", &no_push_options,\n+\t\t\t \"disable push options\"),\n+\t\tOPT_BOOL(0, \"die-version\", &die_version,\n+\t\t\t \"die during version negotiation\"),\n+\t\tOPT_BOOL(0, \"die-readline\", &die_readline,\n+\t\t\t \"die when readline\"),\n+\t\tOPT_STRING_LIST('r', \"return\", &returns, \"old/new/ref/status/msg\",\n+\t\t\t\t\"return of results\"),\n+\t\tOPT__VERBOSE(&verbose, \"be verbose\"),\n+\t\tOPT_INTEGER('V', \"version\", &version,\n+\t\t\t    \"use this protocol version number\"),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, \"test-tools\", options, proc_receive_usage, 0);\n+\tif (argc > 0)\n+\t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n+\n+\tpacket_reader_init(&reader, 0, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\tproc_receive_verison(&reader);\n+\tproc_receive_read_commands(&reader, &commands);\n+\tproc_receive_read_push_options(&reader, &push_options);\n+\n+\tif (verbose) {\n+\t\tstruct command *cmd;\n+\n+\t\tif (use_push_options || use_atomic)\n+\t\t\tfprintf(stderr, \"proc-receive:%s%s\\n\",\n+\t\t\t\tuse_atomic? \" atomic\": \"\",\n+\t\t\t\tuse_push_options ? \" push_options\": \"\");\n+\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tfprintf(stderr, \"proc-receive< %s %s %s\\n\",\n+\t\t\t\toid_to_hex(&cmd->old_oid),\n+\t\t\t\toid_to_hex(&cmd->new_oid),\n+\t\t\t\tcmd->ref_name);\n+\n+\t\tif (push_options.nr > 0)\n+\t\t\tfor_each_string_list_item(item, &push_options)\n+\t\t\t\tfprintf(stderr, \"proc-receive< %s\\n\", item->string);\n+\n+\t\tif (returns.nr)\n+\t\t\tfor_each_string_list_item(item, &returns)\n+\t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n+\t}\n+\n+\tif (returns.nr)\n+\t\tfor_each_string_list_item(item, &returns)\n+\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n+\tpacket_flush(1);\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 590b2efca7..a0d3966b29 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -46,6 +46,7 @@ static struct test_cmd cmds[] = {\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\n+\t{ \"proc-receive\", cmd__proc_receive},\n \t{ \"progress\", cmd__progress },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex ddc8e990e9..07034d3f38 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -35,6 +35,7 @@ int cmd__parse_pathspec_file(int argc, const char** argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\n+int cmd__proc_receive(int argc, const char **argv);\n int cmd__progress(int argc, const char **argv);\n int cmd__reach(int argc, const char **argv);\n int cmd__read_cache(int argc, const char **argv);\ndiff --git a/t/t5411/test-0011-no-hook-error.sh b/t/t5411/test-0011-no-hook-error.sh\nnew file mode 100644\nindex 0000000000..bb6ec92a92\n--- /dev/null\n+++ b/t/t5411/test-0011-no-hook-error.sh\n@@ -0,0 +1,64 @@\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\t$B:master \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] <COMMIT-B> -> master (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0012-no-hook-error--porcelain.sh b/t/t5411/test-0012-no-hook-error--porcelain.sh\nnew file mode 100644\nindex 0000000000..de3a2cebef\n--- /dev/null\n+++ b/t/t5411/test-0012-no-hook-error--porcelain.sh\n@@ -0,0 +1,66 @@\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain --atomic origin \\\n+\t\t$B:master \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t!    <COMMIT-B>:refs/heads/master    [remote rejected] (fail to run proc-receive hook)\n+\t!    HEAD:refs/heads/next    [remote rejected] (fail to run proc-receive hook)\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh\nnew file mode 100644\nindex 0000000000..c79983a7b9\n--- /dev/null\n+++ b/t/t5411/test-0013-bad-protocol.sh\n@@ -0,0 +1,225 @@\n+test_expect_success \"setup proc-receive hook (unknown version, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\t# Check status report for git-push\n+\tsed -n \\\n+\t\t-e \"/^To / { p; n; p; }\" \\\n+\t\t<actual >actual-report &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual-report &&\n+\n+\t# Check error message from \"receive-pack\", but ignore unstable fatal error\n+\t# message (\"remote: fatal: the remote end hung up unexpectedly\") which\n+\t# is different from the remote HTTP server with different locale settings.\n+\tgrep \"^remote: error:\" <actual >actual-error &&\n+\tcat >expect <<-EOF &&\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tEOF\n+\ttest_cmp expect actual-error &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-version, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-version\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-version, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: fatal: bad protocol version: 1\n+\tremote: error: proc-receive version \"0\" is not supported\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-readline, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-readline\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-readline, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: fatal: protocol error: expected \"old new ref\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok\n+\tremote: error: proc-receive reported incomplete status line: \"ok\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"xx refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> xx refs/for/master/topic\n+\tremote: error: proc-receive reported bad status \"xx\" on ref \"refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0014-bad-protocol--porcelain.sh b/t/t5411/test-0014-bad-protocol--porcelain.sh\nnew file mode 100644\nindex 0000000000..e56a76a5ac\n--- /dev/null\n+++ b/t/t5411/test-0014-bad-protocol--porcelain.sh\n@@ -0,0 +1,160 @@\n+test_expect_success \"setup proc-receive hook (unknown version, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\t# Check status report for git-push\n+\tsed -n \\\n+\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n+\t\t<actual >actual-report &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual-report &&\n+\n+\t# Check error message from \"receive-pack\", but ignore unstable fatal error\n+\t# message (\"remote: fatal: the remote end hung up unexpectedly\") which\n+\t# is different from the remote HTTP server with different locale settings.\n+\tgrep \"^remote: error:\" <actual >actual-error &&\n+\tcat >expect <<-EOF &&\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tEOF\n+\ttest_cmp expect actual-error &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok\n+\tremote: error: proc-receive reported incomplete status line: \"ok\"\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"xx refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> xx refs/for/master/topic\n+\tremote: error: proc-receive reported bad status \"xx\" on ref \"refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0020-report-ng.sh b/t/t5411/test-0020-report-ng.sh\nnew file mode 100644\nindex 0000000000..f726b7ca9c\n--- /dev/null\n+++ b/t/t5411/test-0020-report-ng.sh\n@@ -0,0 +1,67 @@\n+test_expect_success \"setup proc-receive hook (ng, no message, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic error msg\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0021-report-ng--porcelain.sh b/t/t5411/test-0021-report-ng--porcelain.sh\nnew file mode 100644\nindex 0000000000..2defe93bc9\n--- /dev/null\n+++ b/t/t5411/test-0021-report-ng--porcelain.sh\n@@ -0,0 +1,69 @@\n+test_expect_success \"setup proc-receive hook (ng, no message, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (failed)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic error msg\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (error msg)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0022-report-unexpect-ref.sh b/t/t5411/test-0022-report-unexpect-ref.sh\nnew file mode 100644\nindex 0000000000..92a415b929\n--- /dev/null\n+++ b/t/t5411/test-0022-report-unexpect-ref.sh\n@@ -0,0 +1,45 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: error: proc-receive reported status on unexpected ref: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\ndiff --git a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\nnew file mode 100644\nindex 0000000000..d3a5b421ee\n--- /dev/null\n+++ b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n@@ -0,0 +1,46 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: error: proc-receive reported status on unexpected ref: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\ndiff --git a/t/t5411/test-0024-report-unknown-ref.sh b/t/t5411/test-0024-report-unknown-ref.sh\nnew file mode 100644\nindex 0000000000..c3946f329a\n--- /dev/null\n+++ b/t/t5411/test-0024-report-unknown-ref.sh\n@@ -0,0 +1,34 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: report unknown reference ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0025-report-unknown-ref--porcelain.sh b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\nnew file mode 100644\nindex 0000000000..b2dd4a4173\n--- /dev/null\n+++ b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\n@@ -0,0 +1,35 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: report unknown reference ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/a/b/c/my/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh\nnew file mode 100644\nindex 0000000000..d0c4da8b23\n--- /dev/null\n+++ b/t/t5411/test-0026-push-options.sh\n@@ -0,0 +1,79 @@\n+test_expect_success \"setup proc-receive hook and disable push-options ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions false &&\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"proc-receive: not support push options ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n+\t\tactual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"proc-receive: push with options ($PROTOCOL)\" '\n+\tgit -C workbench push \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\ndiff --git a/t/t5411/test-0027-push-options--porcelain.sh b/t/t5411/test-0027-push-options--porcelain.sh\nnew file mode 100644\nindex 0000000000..c3461232b8\n--- /dev/null\n+++ b/t/t5411/test-0027-push-options--porcelain.sh\n@@ -0,0 +1,82 @@\n+test_expect_success \"setup proc-receive hook and disable push-options ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions false &&\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"proc-receive: not support push options ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t--porcelain \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n+\t\tactual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"proc-receive: push with options ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push \\\n+\t\t--porcelain \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\ndiff --git a/t/t5411/test-0030-report-ok.sh b/t/t5411/test-0030-report-ok.sh\nnew file mode 100644\nindex 0000000000..44c99d3831\n--- /dev/null\n+++ b/t/t5411/test-0030-report-ok.sh\n@@ -0,0 +1,35 @@\n+test_expect_success \"setup proc-receive hook (ok, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0031-report-ok--porcelain.sh b/t/t5411/test-0031-report-ok--porcelain.sh\nnew file mode 100644\nindex 0000000000..58877ef914\n--- /dev/null\n+++ b/t/t5411/test-0031-report-ok--porcelain.sh\n@@ -0,0 +1,36 @@\n+test_expect_success \"setup proc-receive hook (ok, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nnew file mode 100644\nindex 0000000000..a743aa8018\n--- /dev/null\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -0,0 +1,256 @@\n+test_expect_success \"setup proc-receive hook (option without matching ok, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option without matching ok ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: error: proc-receive reported \"option\" without a matching \"ok/ng\" directive\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and forced-update, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and forced-update ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and old-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid and new-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (report with multiple rewrites, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/a/b/c/topic\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option forced-update\" \\\n+\t\t-r \"option new-oid $A\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/for/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/a/b/c/topic\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option forced-update\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/for/a/b/c/topic\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nnew file mode 100644\nindex 0000000000..8a50d7efe3\n--- /dev/null\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -0,0 +1,265 @@\n+test_expect_success \"setup proc-receive hook (option without matching ok, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option without matching ok ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: error: proc-receive reported \"option\" without a matching \"ok/ng\" directive\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and forced-update, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and forced-update ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and old-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid and new-oid, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (report with multiple rewrites, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/a/b/c/topic\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option forced-update\" \\\n+\t\t-r \"option new-oid $A\"\n+\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/for/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/a/b/c/topic\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option forced-update\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/next/topic    [new reference]\n+\t*    HEAD:refs/for/a/b/c/topic    [new reference]\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0034-report-ft.sh b/t/t5411/test-0034-report-ft.sh\nnew file mode 100644\nindex 0000000000..aca2b0676c\n--- /dev/null\n+++ b/t/t5411/test-0034-report-ft.sh\n@@ -0,0 +1,44 @@\n+test_expect_success \"setup proc-receive hook (ft, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option fall-through\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(B)\n+test_expect_success \"proc-receive: fall throught, let receive-pack to execute ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option fall-through\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] <COMMIT-B> -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/for/master/topic\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             refs/for/master/topic(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/for/master/topic\n+'\ndiff --git a/t/t5411/test-0035-report-ft--porcelain.sh b/t/t5411/test-0035-report-ft--porcelain.sh\nnew file mode 100644\nindex 0000000000..114a14b544\n--- /dev/null\n+++ b/t/t5411/test-0035-report-ft--porcelain.sh\n@@ -0,0 +1,45 @@\n+test_expect_success \"setup proc-receive hook (fall-through, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option fall-through\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(B)\n+test_expect_success \"proc-receive: fall throught, let receive-pack to execute ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\t$B:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option fall-through\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    <COMMIT-B>:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/for/master/topic\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             refs/for/master/topic(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/for/master/topic\n+'\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nnew file mode 100644\nindex 0000000000..12acf9ea1f\n--- /dev/null\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -0,0 +1,159 @@\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/23/123/1\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/2\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/23/123/1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/2\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\nnew file mode 100644\nindex 0000000000..57dc0d17ac\n--- /dev/null\n+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n@@ -0,0 +1,162 @@\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/23/123/1\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/2\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/23/123/1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/2\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nnew file mode 100644\nindex 0000000000..9853e47693\n--- /dev/null\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -0,0 +1,79 @@\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t * [new branch] HEAD -> bar\n+\t * [new branch] HEAD -> baz\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new branch] HEAD -> foo\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit update-ref -d refs/heads/foo &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+'\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nnew file mode 100644\nindex 0000000000..b3953c749b\n--- /dev/null\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -0,0 +1,81 @@\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n+\t*    HEAD:refs/heads/bar    [new branch]\n+\t*    HEAD:refs/heads/baz    [new branch]\n+\t*    HEAD:refs/for/next/topic    [new reference]\n+\t*    HEAD:refs/heads/foo    [new branch]\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit update-ref -d refs/heads/foo &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+\n+'\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"397097","messageId":"20200505144131.30048-6-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"CANYiYbFCG1_hdy1kA06wjrvGSQ2=y-hJX3Rsh-rn=X9tcqv1VQ@mail.gmail.com","subject":"[PATCH v14 5/7] refs.c: refactor to reuse ref_is_hidden()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-05T14:41:29Z","receivedAt":"2020-05-05T14:41:55Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd new function `ref_is_matched()` to reuse `ref_is_hidden()`. Will use\nthis function for `receive-pack` to check commands with specific\nprefixes.\n\nTest case t5512 covered this change.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n refs.c | 13 +++++++++----\n refs.h |  1 +\n 2 files changed, 10 insertions(+), 4 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 224ff66c7b..76a91ac0f6 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1344,14 +1344,14 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti\n \treturn 0;\n }\n \n-int ref_is_hidden(const char *refname, const char *refname_full)\n+int ref_matches(struct string_list *match_refs, const char *refname, const char *refname_full)\n {\n \tint i;\n \n-\tif (!hide_refs)\n+\tif (!match_refs)\n \t\treturn 0;\n-\tfor (i = hide_refs->nr - 1; i >= 0; i--) {\n-\t\tconst char *match = hide_refs->items[i].string;\n+\tfor (i = match_refs->nr - 1; i >= 0; i--) {\n+\t\tconst char *match = match_refs->items[i].string;\n \t\tconst char *subject;\n \t\tint neg = 0;\n \t\tconst char *p;\n@@ -1377,6 +1377,11 @@ int ref_is_hidden(const char *refname, const char *refname_full)\n \treturn 0;\n }\n \n+int ref_is_hidden(const char *refname, const char *refname_full)\n+{\n+\treturn ref_matches(hide_refs, refname, refname_full);\n+}\n+\n const char *find_descendant_ref(const char *dirname,\n \t\t\t\tconst struct string_list *extras,\n \t\t\t\tconst struct string_list *skip)\ndiff --git a/refs.h b/refs.h\nindex a92d2c74c8..9907dd12b0 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -727,6 +727,7 @@ int parse_hide_refs_config(const char *var, const char *value, const char *);\n  * parameter always points to the full ref name.\n  */\n int ref_is_hidden(const char *, const char *);\n+int ref_matches(struct string_list *, const char *, const char *);\n \n enum ref_type {\n \tREF_TYPE_PER_WORKTREE,\t  /* refs inside refs/ but not shared       */\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"397101","messageId":"20200505152521.30530-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200505144131.30048-4-worldhello.net@gmail.com","subject":"[PATCH v14 8/7] fixup! New capability \"report-status-v2\" for git-push","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-05T15:25:21Z","receivedAt":"2020-05-05T15:25:31Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nFixup commit for patch v14 3/7.  Will queue this fix for reroll v15 with others.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/send-pack.c | 2 +-\n send-pack.c         | 3 +--\n 2 files changed, 2 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 4faee72640..4fff1c62d3 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -109,7 +109,7 @@ static void print_helper_status(struct ref *ref)\n \t\t\t\tstrbuf_addf(&buf, \"option new-oid %s\\n\",\n \t\t\t\t\t\t  oid_to_hex(options->new_oid));\n \t\t\tif (options->forced_update)\n-\t\t\t\tstrbuf_addf(&buf, \"option forced-update\\n\");\n+\t\t\t\tstrbuf_addstr(&buf, \"option forced-update\\n\");\n \t\t}\n \t\twrite_or_die(1, buf.buf, buf.len);\n \t}\ndiff --git a/send-pack.c b/send-pack.c\nindex 9a9285755c..82b9760340 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -253,8 +253,7 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \t\t\t\thint->report.error_message = \"failed\";\n \t\t} else {\n \t\t\thint->status = REF_STATUS_OK;\n-\t\t\tif (p)\n-\t\t\t\thint->report.message = xstrdup(p);\n+\t\t\thint->report.message = xstrdup_or_null(p);\n \t\t}\n \t\tnew_options = 1;\n \t}\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"397250","messageId":"xmqq4kss64h7.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200505144131.30048-1-worldhello.net@gmail.com","subject":"Re: [PATCH v14 0/7] New proc-receive hook for centralized workflow","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-05-06T23:14:28Z","receivedAt":"2020-05-06T23:14:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n>\n> This topic introduces a new hook named \"proc-receive\" for\n> \"receive-pack\".  We can use this hook to implement a Gerrit-like\n> centralized workflow.\n\nhttps://github.com/git/git/runs/651179641 shows us that osx-clang\njob ends like so:\n\nThanks.\n\n\nTest Summary Report\n-------------------\nt5411-proc-receive-hook.sh                       (Wstat: 256 Tests: 273 Failed: 0)\n  Non-zero exit status: 1\n  Parse errors: Tests out of sequence.  Found (27) but expected (26)\n                Tests out of sequence.  Found (28) but expected (27)\n                Tests out of sequence.  Found (29) but expected (28)\n                Tests out of sequence.  Found (30) but expected (29)\n                Tests out of sequence.  Found (31) but expected (30)\nDisplayed the first 5 of 249 TAP syntax errors.\nRe-run prove with the -p option to see them all.\nFiles=906, Tests=22778, 1347 wallclock secs ( 8.21 usr  3.04 sys + 1322.42 cusr 2031.60 csys = 3365.27 CPU)\nResult: FAIL\nmake[1]: *** [prove] Error 1\nmake: *** [test] Error 2\n##[error]Process completed with exit code 2.\n"},{"id":"397253","messageId":"CANYiYbGf-szLeBP_JVpCYaWczeg1rNavDCXtEuwXUqiLNoUFtQ@mail.gmail.com","threadId":"52936","inReplyTo":"xmqq4kss64h7.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v14 0/7] New proc-receive hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-07T01:37:04Z","receivedAt":"2020-05-07T01:37:18Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2020年5月7日周四 上午7:14写道：\n>\n> Jiang Xin <worldhello.net@gmail.com> writes:\n>\n> > From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n> >\n> > This topic introduces a new hook named \"proc-receive\" for\n> > \"receive-pack\".  We can use this hook to implement a Gerrit-like\n> > centralized workflow.\n>\n> https://github.com/git/git/runs/651179641 shows us that osx-clang\n> job ends like so:\n>\n> Thanks.\n>\n>\n> Test Summary Report\n> -------------------\n> t5411-proc-receive-hook.sh                       (Wstat: 256 Tests: 273 Failed: 0)\n>   Non-zero exit status: 1\n>   Parse errors: Tests out of sequence.  Found (27) but expected (26)\n>                 Tests out of sequence.  Found (28) but expected (27)\n>                 Tests out of sequence.  Found (29) but expected (28)\n>                 Tests out of sequence.  Found (30) but expected (29)\n>                 Tests out of sequence.  Found (31) but expected (30)\n\nIt seems that the status report for git-push (\"ok <refname>\") confuses\nthe test. Will fix it in next reroll.\n"},{"id":"397281","messageId":"CANYiYbHi90euK_O8KwPjikuSimWzScV7jT5yoxvT6pFHPJhRdw@mail.gmail.com","threadId":"52936","inReplyTo":"CANYiYbGf-szLeBP_JVpCYaWczeg1rNavDCXtEuwXUqiLNoUFtQ@mail.gmail.com","subject":"Re: [PATCH v14 0/7] New proc-receive hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-07T11:18:46Z","receivedAt":"2020-05-07T11:19:00Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> 于2020年5月7日周四 上午9:37写道：\n>\n> Junio C Hamano <gitster@pobox.com> 于2020年5月7日周四 上午7:14写道：\n> >\n> > Jiang Xin <worldhello.net@gmail.com> writes:\n> >\n> > > From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n> > >\n> > > This topic introduces a new hook named \"proc-receive\" for\n> > > \"receive-pack\".  We can use this hook to implement a Gerrit-like\n> > > centralized workflow.\n> >\n> > https://github.com/git/git/runs/651179641 shows us that osx-clang\n> > job ends like so:\n> >\n> > Thanks.\n> >\n> >\n> > Test Summary Report\n> > -------------------\n> > t5411-proc-receive-hook.sh                       (Wstat: 256 Tests: 273 Failed: 0)\n> >   Non-zero exit status: 1\n> >   Parse errors: Tests out of sequence.  Found (27) but expected (26)\n> >                 Tests out of sequence.  Found (28) but expected (27)\n> >                 Tests out of sequence.  Found (29) but expected (28)\n> >                 Tests out of sequence.  Found (30) but expected (29)\n> >                 Tests out of sequence.  Found (31) but expected (30)\n>\n> It seems that the status report for git-push (\"ok <refname>\") confuses\n> the test. Will fix it in next reroll.\n\nThis morning I cannot find the specific error from the build log, but\nnow I find the error output inside the job tagged as \"Run\nci/print-test-failures.sh\":\n\n    ++++ diff -u expect actual\n    --- expect 2020-05-06 22:47:02.000000000 +0000\n    +++ actual 2020-05-06 22:47:02.000000000 +0000\n    @@ -2,5 +2,6 @@\n    remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    remote: # proc-receive hook\n    remote: fatal: protocol error: expected \"old new ref\", got\n\"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n    -To <URL/of/upstream.git>\n    - ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive\nfailed to report status)\n    +fatal: unable to write flush packet: Broken pipe\n    +fatal: the remote end hung up unexpectedly\n    +fatal: the remote end hung up unexpectedly\n\nIn order to increase the code coverage (thanks Stolee for sending a\nnice code coverage report), I add an option \"--die-readline\" for\ntest-helper (t/helper/test-proc-receive):\n\n                if (parse_oid_hex(reader->line, &old_oid, &p) ||\n                    *p++ != ' ' ||\n                    parse_oid_hex(p, &new_oid, &p) ||\n                    *p++ != ' ' ||\n                    die_readline)\n                        die(\"protocol error: expected 'old new ref', got '%s'\",\n                            reader->line);\n\nHook \"proc-receive\" hook will die early with the \"--die_readline\"\noption, and \"receive-pack\" should continue running because\n\"receive-pack\" ignores the SIG_PIPE signal, but I don't know why the\nbehaviors of \"receive-pack\" are quite different on different\nplatforms.\n\nI will rewrite the test case using \"grep\" instead of \"test_cmp\", and\nmay send another reroll tonight.\n\n--\nJiang Xin\n"},{"id":"397314","messageId":"20200507161057.12690-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqq4kss64h7.fsf@gitster.c.googlers.com","subject":"[PATCH v15 1/7] transport: not report a non-head push as a branch","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-07T16:10:51Z","receivedAt":"2020-05-07T16:11:07Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing a new reference (not a head or tag), report it as a new\nreference instead of a new branch.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411-proc-receive-hook.sh                  | 108 ++++++++++++++++++\n t/t5411/common-functions.sh                   |  56 +++++++++\n t/t5411/test-0000-standard-git-push.sh        |  84 ++++++++++++++\n .../test-0001-standard-git-push--porcelain.sh |  86 ++++++++++++++\n t/t5411/test-0002-pre-receive-declined.sh     |  33 ++++++\n ...st-0003-pre-receive-declined--porcelain.sh |  34 ++++++\n t/t5516-fetch-push.sh                         |   2 +-\n transport.c                                   |   9 +-\n 8 files changed, 408 insertions(+), 4 deletions(-)\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/test-0000-standard-git-push.sh\n create mode 100644 t/t5411/test-0001-standard-git-push--porcelain.sh\n create mode 100644 t/t5411/test-0002-pre-receive-declined.sh\n create mode 100644 t/t5411/test-0003-pre-receive-declined--porcelain.sh\n\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nnew file mode 100755\nindex 0000000000..8a1158cc1e\n--- /dev/null\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -0,0 +1,108 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook'\n+\n+. ./test-lib.sh\n+\n+. \"$TEST_DIRECTORY\"/t5411/common-functions.sh\n+\n+setup_upstream_and_workbench () {\n+\t# Refs of upstream : master(A)\n+\t# Refs of workbench: master(A)  tags/v123\n+\ttest_expect_success \"setup upstream and workbench\" '\n+\t\trm -rf upstream.git &&\n+\t\trm -rf workbench &&\n+\t\tgit init --bare upstream.git &&\n+\t\tgit init workbench &&\n+\t\tcreate_commits_in workbench A B &&\n+\t\t(\n+\t\t\tcd workbench &&\n+\t\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\t\tgit config core.abbrev 7 &&\n+\t\t\tgit remote add origin ../upstream.git &&\n+\t\t\tgit update-ref refs/heads/master $A &&\n+\t\t\tgit tag -m \"v123\" v123 $A &&\n+\t\t\tgit push origin $B:refs/heads/master &&\n+\t\t\tgit -C ../upstream.git update-ref \\\n+\t\t\t\trefs/heads/master $A\n+\t\t) &&\n+\t\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t\t# setup pre-receive hook\n+\t\twrite_script upstream.git/hooks/pre-receive <<-\\EOF &&\n+\t\texec >&2\n+\t\techo \"# pre-receive hook\"\n+\t\twhile read old new ref\n+\t\tdo\n+\t\t\techo \"pre-receive< $old $new $ref\"\n+\t\tdone\n+\t\tEOF\n+\n+\t\t# setup post-receive hook\n+\t\twrite_script upstream.git/hooks/post-receive <<-\\EOF &&\n+\t\texec >&2\n+\t\techo \"# post-receive hook\"\n+\t\twhile read old new ref\n+\t\tdo\n+\t\t\techo \"post-receive< $old $new $ref\"\n+\t\tdone\n+\t\tEOF\n+\n+\t\tupstream=upstream.git\n+\t'\n+}\n+\n+run_proc_receive_hook_test() {\n+\tcase $1 in\n+\thttp)\n+\t\tPROTOCOL=\"HTTP protocol\"\n+\t\tURL_PREFIX=\"http://.*\"\n+\t\t;;\n+\tlocal)\n+\t\tPROTOCOL=\"builtin protocol\"\n+\t\tURL_PREFIX=\"\\.\\.\"\n+\t\t;;\n+\tesac\n+\n+\t# Include test cases for both file and HTTP protocol\n+\tfor t in  \"$TEST_DIRECTORY\"/t5411/test-*.sh\n+\tdo\n+\t\t. \"$t\"\n+\tdone\n+}\n+\n+# Initialize the upstream repository and local workbench.\n+setup_upstream_and_workbench\n+\n+# Run test cases for 'proc-receive' hook on local file protocol.\n+run_proc_receive_hook_test local\n+\n+ROOT_PATH=\"$PWD\"\n+. \"$TEST_DIRECTORY\"/lib-gpg.sh\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+. \"$TEST_DIRECTORY\"/lib-terminal.sh\n+start_httpd\n+\n+# Re-initialize the upstream repository and local workbench.\n+setup_upstream_and_workbench\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup for HTTP protocol\" '\n+\tgit -C upstream.git config http.receivepack true &&\n+\tupstream=\"$HTTPD_DOCUMENT_ROOT_PATH/upstream.git\" &&\n+\tmv upstream.git \"$upstream\" &&\n+\tset_askpass user@host pass@host &&\n+\tgit -C workbench remote set-url origin $HTTPD_URL/smart/upstream.git\n+'\n+\n+setup_askpass_helper\n+\n+# Run test cases for 'proc-receive' hook on HTTP protocol.\n+run_proc_receive_hook_test http\n+\n+test_done\ndiff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\nnew file mode 100644\nindex 0000000000..6580bebd8e\n--- /dev/null\n+++ b/t/t5411/common-functions.sh\n@@ -0,0 +1,56 @@\n+# Create commits in <repo> and assign each commit's oid to shell variables\n+# given in the arguments (A, B, and C). E.g.:\n+#\n+#     create_commits_in <repo> A B C\n+#\n+# NOTE: Never calling this function from a subshell since variable\n+# assignments will disappear when subshell exits.\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{} --)\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  We can easily prepare the expect text\n+# without having to worry about future changes of the commit ID and spaces\n+# of the output.  Single quotes are replaced with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect text.  We also\n+# remove some locale error messages, which break test if we turn on\n+# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n+# on plumbing commands.\n+make_user_friendly_and_stable_output () {\n+\tsed \\\n+\t\t-e \"s/  *\\$//\" \\\n+\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/'/\\\"/g\" \\\n+\t\t-e \"s/\t/    /g\" \\\n+\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n+\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n+\t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n+\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n+\t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n+\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\" \\\n+\t\t-e \"s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#\" \\\n+\t\t-e \"/^error: / d\"\n+}\ndiff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh\nnew file mode 100644\nindex 0000000000..52160aebf7\n--- /dev/null\n+++ b/t/t5411/test-0000-standard-git-push.sh\n@@ -0,0 +1,84 @@\n+# Refs of upstream : master(A)  \n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t * [new branch] HEAD -> next\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"git-push -f ($PROTOCOL)\" '\n+\tgit -C workbench push -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tHEAD:refs/heads/master \\\n+\t\tHEAD:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo <URL/of/upstream.git>\n+\t + <OID-B>...<OID-A> HEAD -> master (forced update)\n+\t - [deleted] next\n+\t * [new tag] v123 -> v123\n+\t * [new reference] HEAD -> refs/review/master/topic\n+\t * [new branch] HEAD -> a/b/c\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\ndiff --git a/t/t5411/test-0001-standard-git-push--porcelain.sh b/t/t5411/test-0001-standard-git-push--porcelain.sh\nnew file mode 100644\nindex 0000000000..4077015b17\n--- /dev/null\n+++ b/t/t5411/test-0001-standard-git-push--porcelain.sh\n@@ -0,0 +1,86 @@\n+# Refs of upstream : master(A)  \n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"git-push -f ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tHEAD:refs/heads/master \\\n+\t\tHEAD:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo <URL/of/upstream.git>\n+\t+    HEAD:refs/heads/master    <OID-B>...<OID-A> (forced update)\n+\t-    :refs/heads/next    [deleted]\n+\t*    refs/tags/v123:refs/tags/v123    [new tag]\n+\t*    HEAD:refs/review/master/topic    [new reference]\n+\t*    HEAD:refs/heads/a/b/c    [new branch]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\ndiff --git a/t/t5411/test-0002-pre-receive-declined.sh b/t/t5411/test-0002-pre-receive-declined.sh\nnew file mode 100644\nindex 0000000000..c246f7e68e\n--- /dev/null\n+++ b/t/t5411/test-0002-pre-receive-declined.sh\n@@ -0,0 +1,33 @@\n+test_expect_success \"setup pre-receive hook ($PROTOCOL)\" '\n+\tmv \"$upstream/hooks/pre-receive\" \"$upstream/hooks/pre-receive.ok\" &&\n+\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n+\texit 1\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push is declined ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] <COMMIT-B> -> master (pre-receive hook declined)\n+\t ! [remote rejected] HEAD -> next (pre-receive hook declined)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tmv \"$upstream/hooks/pre-receive.ok\" \"$upstream/hooks/pre-receive\"\n+'\ndiff --git a/t/t5411/test-0003-pre-receive-declined--porcelain.sh b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\nnew file mode 100644\nindex 0000000000..510cd3b333\n--- /dev/null\n+++ b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\n@@ -0,0 +1,34 @@\n+test_expect_success \"setup pre-receive hook ($PROTOCOL)\" '\n+\tmv \"$upstream/hooks/pre-receive\" \"$upstream/hooks/pre-receive.ok\" &&\n+\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n+\texit 1\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push is declined ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    <COMMIT-B>:refs/heads/master    [remote rejected] (pre-receive hook declined)\n+\t!    HEAD:refs/heads/next    [remote rejected] (pre-receive hook declined)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tmv \"$upstream/hooks/pre-receive.ok\" \"$upstream/hooks/pre-receive\"\n+'\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 9c6218f568..77938db77f 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1039,7 +1039,7 @@ test_force_fetch_tag \"annotated tag\" \"-f -a -m'tag message'\"\n test_expect_success 'push --porcelain' '\n \tmk_empty testrepo &&\n \techo >.git/foo  \"To testrepo\" &&\n-\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new branch]\"  &&\n+\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new reference]\"  &&\n \techo >>.git/foo \"Done\" &&\n \tgit push >.git/bar --porcelain  testrepo refs/heads/master:refs/remotes/origin/master &&\n \t(\ndiff --git a/transport.c b/transport.c\nindex 15f5ba4e8f..26fefd13c1 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -500,9 +500,12 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\t\t\t porcelain, summary_width);\n \telse if (is_null_oid(&ref->old_oid))\n \t\tprint_ref_status('*',\n-\t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n-\t\t\t\"[new branch]\"),\n-\t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t  ? \"[new tag]\"\n+\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t     ? \"[new branch]\"\n+\t\t\t\t     : \"[new reference]\")),\n+\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"397315","messageId":"20200507161057.12690-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqq4kss64h7.fsf@gitster.c.googlers.com","subject":"[PATCH v15 0/7] New proc-receive hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-07T16:10:50Z","receivedAt":"2020-05-07T16:11:09Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThis topic introduces a new hook named \"proc-receive\" for\n\"receive-pack\".  We can use this hook to implement a Gerrit-like\ncentralized workflow.\n\n## Changes since v14\n\n1. Fixed broken test case in patch 2/7, and all test cases passed.\n   See: https://github.com/jiangxin/git/runs/653492851\n\n2. Bugfix: if \"proc-receive\" report a \"ng\" status, other successfully\n   updated references will be reported as failure.  Fixed in patch 2/7.\n\n3. Add test case for report status v1 in \"t5411/once-0010\" in patch 3/7.\n\n## Range-diff v14...v15\n\n1:  805db643dd ! 1:  06dd984d35 transport: not report a non-head push as a branch\n    @@ t/t5411-proc-receive-hook.sh (new)\n     +\t\t\tgit remote add origin ../upstream.git &&\n     +\t\t\tgit update-ref refs/heads/master $A &&\n     +\t\t\tgit tag -m \"v123\" v123 $A &&\n    -+\t\t\tgit push origin $A:refs/heads/master\n    ++\t\t\tgit push origin $B:refs/heads/master &&\n    ++\t\t\tgit -C ../upstream.git update-ref \\\n    ++\t\t\t\trefs/heads/master $A\n     +\t\t) &&\n     +\t\tTAG=$(git -C workbench rev-parse v123) &&\n     +\n    @@ t/t5411-proc-receive-hook.sh (new)\n     +\t\tPROTOCOL=\"HTTP protocol\"  +\t\tURL_PREFIX=\"http://.*\"\n     +\t\t;;\n    -+\tfile | local)\n    ++\tlocal)\n     +\t\tPROTOCOL=\"builtin protocol\"\n     +\t\tURL_PREFIX=\"\\.\\.\"\n     +\t\t;;\n2:  d6db85a4e5 ! 2:  1c11a5dcc6 receive-pack: add new proc-receive hook\n    @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n     +\tif (run_proc_receive &&\n     +\t    run_proc_receive_hook(commands, push_options))\n     +\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n    -+\t\t\tif (!cmd->report.error_message  && (cmd->run_proc_receive || use_atomic))\n    ++\t\t\tif (!cmd->report.error_message &&\n    ++\t\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED) &&\n    ++\t\t\t    (cmd->run_proc_receive || use_atomic))\n     +\t\t\t\tcmd->report.error_message = \"fail to run proc-receive hook\";\n     +\n      \tif (use_atomic)\n    @@ t/t5411/test-0013-bad-protocol.sh (new)\n     +\t\t>out 2>&1 &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\n    -+\tcat >expect <<-EOF &&\n    -+\tremote: # pre-receive hook\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: # proc-receive hook\n    -+\tremote: fatal: protocol error: expected \"old new ref\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"\n    -+\tTo <URL/of/upstream.git>\n    -+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n    -+\tEOF\n    -+\ttest_cmp expect actual &&\n    ++\tgrep \"remote: fatal: protocol error: expected \\\"old new ref\\\", got \\\"<ZERO-OID> <COMMIT-A> refs/for/master/topic\\\"\" actual &&\n     +\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n    @@ t/t5411/test-0038-report-mixed-refs.sh (new)\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"ok refs/for/next/topic\" \\\n    ++\t\t-r \"ok refs/for/next/topic2\" \\\n    ++\t\t-r \"ng refs/for/next/topic1 fail to call Web API\" \\\n     +\t\t-r \"ok refs/for/master/topic\" \\\n     +\t\t-r \"option refname refs/for/master/topic\" \\\n     +\t\t-r \"option old-oid $A\" \\\n    @@ t/t5411/test-0038-report-mixed-refs.sh (new)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n     +test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n    -+\tgit -C workbench push origin \\\n    ++\ttest_must_fail git -C workbench push origin \\\n     +\t\t$B:refs/heads/master \\\n     +\t\tHEAD:refs/heads/bar \\\n     +\t\tHEAD:refs/heads/baz \\\n    -+\t\tHEAD:refs/for/next/topic \\\n    ++\t\tHEAD:refs/for/next/topic2 \\\n    ++\t\tHEAD:refs/for/next/topic1 \\\n     +\t\tHEAD:refs/heads/foo \\\n     +\t\tHEAD:refs/for/master/topic \\\n    ++\t\tHEAD:refs/for/next/topic3 \\\n     +\t\t>out 2>&1 &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5411/test-0038-report-mixed-refs.sh (new)\n     +\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n     +\tremote: # proc-receive hook\n    -+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> ok refs/for/next/topic\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n    ++\tremote: proc-receive> ok refs/for/next/topic2\n    ++\tremote: proc-receive> ng refs/for/next/topic1 fail to call Web API\n     +\tremote: proc-receive> ok refs/for/master/topic\n     +\tremote: proc-receive> option refname refs/for/master/topic\n     +\tremote: proc-receive> option old-oid <COMMIT-A>\n    @@ t/t5411/test-0038-report-mixed-refs.sh (new)\n     +\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n     +\t <OID-A>..<OID-B> <COMMIT-B> -> master\n     +\t * [new branch] HEAD -> bar\n     +\t * [new branch] HEAD -> baz\n    -+\t * [new reference] HEAD -> refs/for/next/topic\n    ++\t * [new reference] HEAD -> refs/for/next/topic2\n     +\t * [new branch] HEAD -> foo\n     +\t * [new reference] HEAD -> refs/for/master/topic\n    ++\t ! [remote rejected] HEAD -> refs/for/next/topic1 (fail to call Web API)\n    ++\t ! [remote rejected] HEAD -> refs/for/next/topic3 (proc-receive failed to report status)\n     +\tEOF\n     +\ttest_cmp expect actual &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n    @@ t/t5411/test-0039-report-mixed-refs--porcelain.sh (new)\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v \\\n    -+\t\t-r \"ok refs/for/next/topic\" \\\n    ++\t\t-r \"ok refs/for/next/topic2\" \\\n    ++\t\t-r \"ng refs/for/next/topic1 fail to call Web API\" \\\n     +\t\t-r \"ok refs/for/master/topic\" \\\n     +\t\t-r \"option refname refs/for/master/topic\" \\\n     +\t\t-r \"option old-oid $A\" \\\n    @@ t/t5411/test-0039-report-mixed-refs--porcelain.sh (new)\n     +# Refs of workbench: master(A)  tags/v123\n     +# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n     +test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcelain)\" '\n    -+\tgit -C workbench push --porcelain origin \\\n    ++\ttest_must_fail git -C workbench push --porcelain origin \\\n     +\t\t$B:refs/heads/master \\\n     +\t\tHEAD:refs/heads/bar \\\n     +\t\tHEAD:refs/heads/baz \\\n    -+\t\tHEAD:refs/for/next/topic \\\n    ++\t\tHEAD:refs/for/next/topic2 \\\n    ++\t\tHEAD:refs/for/next/topic1 \\\n     +\t\tHEAD:refs/heads/foo \\\n     +\t\tHEAD:refs/for/master/topic \\\n    ++\t\tHEAD:refs/for/next/topic3 \\\n     +\t\t>out 2>&1 &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5411/test-0039-report-mixed-refs--porcelain.sh (new)\n     +\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n    -+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n     +\tremote: # proc-receive hook\n    -+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n     +\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n    -+\tremote: proc-receive> ok refs/for/next/topic\n    ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n    ++\tremote: proc-receive> ok refs/for/next/topic2\n    ++\tremote: proc-receive> ng refs/for/next/topic1 fail to call Web API\n     +\tremote: proc-receive> ok refs/for/master/topic\n     +\tremote: proc-receive> option refname refs/for/master/topic\n     +\tremote: proc-receive> option old-oid <COMMIT-A>\n    @@ t/t5411/test-0039-report-mixed-refs--porcelain.sh (new)\n     +\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n    -+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tTo <URL/of/upstream.git>\n     +\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n     +\t*    HEAD:refs/heads/bar    [new branch]\n     +\t*    HEAD:refs/heads/baz    [new branch]\n    -+\t*    HEAD:refs/for/next/topic    [new reference]\n    ++\t*    HEAD:refs/for/next/topic2    [new reference]\n     +\t*    HEAD:refs/heads/foo    [new branch]\n     +\t*    HEAD:refs/for/master/topic    [new reference]\n    ++\t!    HEAD:refs/for/next/topic1    [remote rejected] (fail to call Web API)\n    ++\t!    HEAD:refs/for/next/topic3    [remote rejected] (proc-receive failed to report status)\n     +\tDone\n     +\tEOF\n     +\ttest_cmp expect actual &&\n3:  342254d81d ! 3:  c7f731b9f6 New capability \"report-status-v2\" for git-push\n    @@ remote.c: void free_one_ref(struct ref *ref)\n      \tfree(ref->symref);\n      \tfree(ref);\n      }\n    -@@ remote.c: void apply_push_cas(struct push_cas_option *cas,\n    - \tstruct ref *ref;\n    - \tfor (ref = remote_refs; ref; ref = ref->next)\n    - \t\tapply_cas(cas, remote, ref);\n    --}\n    -+}\n    - \\ No newline at end of file\n     \n      ## remote.h ##\n     @@ remote.h: struct ref {\n    @@ send-pack.c: static int receive_status(struct packet_reader *reader, struct ref\n      \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n      \t\t\tbreak;\n     -\t\tif (!starts_with(reader->line, \"ok \") && !starts_with(reader->line, \"ng \")) {\n    +-\t\t\terror(\"invalid ref status from remote: %s\", reader->line);\n     +\t\thead = reader->line;\n     +\t\tp = strchr(head, ' ');\n     +\t\tif (!p) {\n    - \t\t\terror(\"invalid ref status from remote: %s\", reader->line);\n    ++\t\t\terror(\"invalid status line from remote: %s\", reader->line);\n      \t\t\tret = -1;\n      \t\t\tbreak;\n      \t\t}\n    @@ send-pack.c: static int receive_status(struct packet_reader *reader, struct ref\n     -\t\tmsg = strchr(refname, ' ');\n     -\t\tif (msg)\n     -\t\t\t*msg++ = '\\0';\n    -+\t\trefname = p;\n    -+\t\tp = strchr(refname, ' ');\n    -+\t\tif (p)\n    -+\t\t\t*p++ = '\\0';\n    +-\n     +\t\tif (strcmp(head, \"ok\") && strcmp(head, \"ng\")) {\n    -+\t\t\terror(\"invalid ref status from remote: %s\", reader->line);\n    ++\t\t\terror(\"invalid ref status from remote: %s\", head);\n     +\t\t\tret = -1;\n     +\t\t\tbreak;\n     +\t\t}\n    - \n    ++\t\trefname = p;\n    ++\t\tp = strchr(refname, ' ');\n    ++\t\tif (p)\n    ++\t\t\t*p++ = '\\0';\n      \t\t/* first try searching at our hint, falling back to all refs */\n      \t\tif (hint)\n    + \t\t\thint = find_ref_by_name(hint, refname);\n     @@ send-pack.c: static int receive_status(struct packet_reader *reader, struct ref *refs)\n      \t\t\t\t\trefname);\n      \t\t\tcontinue;\n    @@ send-pack.c: int send_pack(struct send_pack_args *args,\n      \t\tstrbuf_addstr(&cap_buf, \" side-band-64k\");\n      \tif (quiet_supported && (args->quiet || !args->progress))\n     \n    + ## t/t5411-proc-receive-hook.sh ##\n    +@@ t/t5411-proc-receive-hook.sh: run_proc_receive_hook_test() {\n    + # Initialize the upstream repository and local workbench.\n    + setup_upstream_and_workbench\n    + \n    ++# Load test cases that only need to be executed once.\n    ++for t in  \"$TEST_DIRECTORY\"/t5411/once-*.sh\n    ++do\n    ++\t. \"$t\"\n    ++done\n    ++\n    + # Run test cases for 'proc-receive' hook on local file protocol.\n    + run_proc_receive_hook_test local\n    + \n    +\n    + ## t/t5411/once-0010-report-status-v1.sh (new) ##\n    +@@\n    ++test_expect_success \"setup proc-receive hook\" '\n    ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    ++\tprintf >&2 \"# proc-receive hook\\n\"\n    ++\ttest-tool proc-receive -v \\\n    ++\t\t-r \"ok refs/for/master/topic1\" \\\n    ++\t\t-r \"option fall-through\" \\\n    ++\t\t-r \"ok refs/for/master/topic2\" \\\n    ++\t\t-r \"option refname refs/for/changes/23/123/1\" \\\n    ++\t\t-r \"option new-oid $A\" \\\n    ++\t\t-r \"ok refs/for/master/topic2\" \\\n    ++\t\t-r \"option refname refs/for/changes/24/124/2\" \\\n    ++\t\t-r \"option old-oid $B\" \\\n    ++\t\t-r \"option new-oid $A\" \\\n    ++\t\t-r \"option forced-update\" \\\n    ++\t\t-r \"ng refs/for/next/topic target branch not exist\"\n    ++\tEOF\n    ++'\n    ++\n    ++# Refs of upstream : master(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++# git push         : (B)                   refs/for/master/topic1(A)  foo(A)  refs/for/next/topic(A)  refs/for/master/topic2(A)\n    ++test_expect_success \"proc-receive: report status v1\" '\n    ++\t{\n    ++\t\tprintf \"%s %s refs/heads/master\\0report-status\\n\" \\\n    ++\t\t\t$A $B | packetize &&\n    ++\t\tprintf \"%s %s refs/for/master/topic1\\n\" \\\n    ++\t\t\t$ZERO_OID $A | packetize &&\n    ++\t\tprintf \"%s %s refs/heads/foo\\n\" \\\n    ++\t\t\t$ZERO_OID $A | packetize &&\n    ++\t\tprintf \"%s %s refs/for/next/topic\\n\" \\\n    ++\t\t\t$ZERO_OID $A | packetize &&\n    ++\t\tprintf \"%s %s refs/for/master/topic2\\n\" \\\n    ++\t\t\t$ZERO_OID $A | packetize &&\n    ++\t\tprintf 0000 &&\n    ++\t\tprintf \"\" | git -C \"$upstream\" pack-objects --stdout\n    ++\t} | git receive-pack \"$upstream\" --stateless-rpc \\\n    ++\t>out 2>&1 &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t# pre-receive hook\n    ++\tpre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n    ++\tpre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n    ++\tpre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n    ++\tpre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tpre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2\n    ++\t# proc-receive hook\n    ++\tproc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n    ++\tproc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    ++\tproc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2\n    ++\tproc-receive> ok refs/for/master/topic1\n    ++\tproc-receive> option fall-through\n    ++\tproc-receive> ok refs/for/master/topic2\n    ++\tproc-receive> option refname refs/for/changes/23/123/1\n    ++\tproc-receive> option new-oid <COMMIT-A>\n    ++\tproc-receive> ok refs/for/master/topic2\n    ++\tproc-receive> option refname refs/for/changes/24/124/2\n    ++\tproc-receive> option old-oid <COMMIT-B>\n    ++\tproc-receive> option new-oid <COMMIT-A>\n    ++\tproc-receive> option forced-update\n    ++\tproc-receive> ng refs/for/next/topic target branch not exist\n    ++\t000eunpack ok\n    ++\t0019ok refs/heads/master\n    ++\t001eok refs/for/master/topic1\n    ++\t0016ok refs/heads/foo\n    ++\t0033ng refs/for/next/topic target branch not exist\n    ++\t001eok refs/for/master/topic2\n    ++\t0000# post-receive hook\n    ++\tpost-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n    ++\tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n    ++\tpost-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n    ++\tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2\n    ++\tEOF\n    ++\ttest_cmp expect actual &&\n    ++\n    ++\tgit -C \"$upstream\" show-ref >out &&\n    ++\tmake_user_friendly_and_stable_output <out >actual &&\n    ++\tcat >expect <<-EOF &&\n    ++\t<COMMIT-A> refs/for/master/topic1\n    ++\t<COMMIT-A> refs/heads/foo\n    ++\t<COMMIT-B> refs/heads/master\n    ++\tEOF\n    ++\ttest_cmp expect actual\n    ++'\n    ++\n    ++# Refs of upstream : master(B)             foo(A)  refs/for/master/topic1(A)\n    ++# Refs of workbench: master(A)  tags/v123\n    ++test_expect_success \"cleanup\" '\n    ++\t(\n    ++\t\tcd \"$upstream\" &&\n    ++\t\trm -f \"hooks/proc-receive\" &&\n    ++\t\tgit update-ref refs/heads/master $A &&\n    ++\t\tgit update-ref -d refs/for/master/topic1 &&\n    ++\t\tgit update-ref -d refs/heads/foo\n    ++\t)\n    ++'\n    +\n      ## t/t5411/test-0032-report-with-options.sh ##\n     @@ t/t5411/test-0032-report-with-options.sh: test_expect_success \"proc-receive: report option refname ($PROTOCOL)\" '\n      \tremote: # post-receive hook\n    @@ t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh: test_expect_su\n      ## t/t5411/test-0038-report-mixed-refs.sh ##\n     @@ t/t5411/test-0038-report-mixed-refs.sh: test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n      \t * [new branch] HEAD -> baz\n    - \t * [new reference] HEAD -> refs/for/next/topic\n    + \t * [new reference] HEAD -> refs/for/next/topic2\n      \t * [new branch] HEAD -> foo\n     -\t * [new reference] HEAD -> refs/for/master/topic\n     +\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n    + \t ! [remote rejected] HEAD -> refs/for/next/topic1 (fail to call Web API)\n    + \t ! [remote rejected] HEAD -> refs/for/next/topic3 (proc-receive failed to report status)\n      \tEOF\n    - \ttest_cmp expect actual &&\n    - \tgit -C \"$upstream\" show-ref >out &&\n     \n      ## t/t5411/test-0039-report-mixed-refs--porcelain.sh ##\n     @@ t/t5411/test-0039-report-mixed-refs--porcelain.sh: test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcel\n      \t*    HEAD:refs/heads/baz    [new branch]\n    - \t*    HEAD:refs/for/next/topic    [new reference]\n    + \t*    HEAD:refs/for/next/topic2    [new reference]\n      \t*    HEAD:refs/heads/foo    [new branch]\n     -\t*    HEAD:refs/for/master/topic    [new reference]\n     +\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n    + \t!    HEAD:refs/for/next/topic1    [remote rejected] (fail to call Web API)\n    + \t!    HEAD:refs/for/next/topic3    [remote rejected] (proc-receive failed to report status)\n      \tDone\n    - \tEOF\n    - \ttest_cmp expect actual &&\n     \n      ## transport-helper.c ##\n     @@ transport-helper.c: static int push_update_ref_status(struct strbuf *buf,\n4:  5c9b732a38 ! 4:  11b917121f receive-pack: feed report options to post-receive\n    @@ builtin/receive-pack.c: static int feed_receive_hook(void *state_, const char **\n      \t\t*bufp = state->buf.buf;\n      \t\t*sizep = state->buf.len;\n     \n    + ## t/t5411/once-0010-report-status-v1.sh ##\n    +@@ t/t5411/once-0010-report-status-v1.sh: test_expect_success \"proc-receive: report status v1\" '\n    + \tpost-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n    + \tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n    + \tpost-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n    +-\tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2\n    ++\tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/changes/23/123/1\n    ++\tpost-receive< <COMMIT-B> <COMMIT-A> refs/for/changes/24/124/2\n    + \tEOF\n    + \ttest_cmp expect actual &&\n    + \n    +\n      ## t/t5411/test-0032-report-with-options.sh ##\n     @@ t/t5411/test-0032-report-with-options.sh: test_expect_success \"proc-receive: report option refname ($PROTOCOL)\" '\n      \tremote: proc-receive> ok refs/for/master/topic\n    @@ t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh: test_expect_su\n      ## t/t5411/test-0038-report-mixed-refs.sh ##\n     @@ t/t5411/test-0038-report-mixed-refs.sh: test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n      \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n    - \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n      \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n     -\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n    @@ t/t5411/test-0038-report-mixed-refs.sh: test_expect_success \"proc-receive: repor\n      ## t/t5411/test-0039-report-mixed-refs--porcelain.sh ##\n     @@ t/t5411/test-0039-report-mixed-refs--porcelain.sh: test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcel\n      \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n    - \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n    + \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n      \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n     -\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n     +\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n5:  cf0774127f = 5:  7b84236103 refs.c: refactor to reuse ref_is_hidden()\n6:  8053b06b09 ! 6:  9d9032285b receive-pack: new config receive.procReceiveRefs\n    @@ builtin/receive-pack.c: int cmd_receive_pack(int argc, const char **argv, const\n      \treturn 0;\n      }\n     \n    + ## t/t5411/once-0010-report-status-v1.sh ##\n    +@@\n    ++test_expect_success \"setup receive.procReceiveRefs\" '\n    ++\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs/for\n    ++'\n    ++\n    + test_expect_success \"setup proc-receive hook\" '\n    + \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n    + \tprintf >&2 \"# proc-receive hook\\n\"\n    +@@ t/t5411/once-0010-report-status-v1.sh: test_expect_success \"proc-receive: report status v1\" '\n    + test_expect_success \"cleanup\" '\n    + \t(\n    + \t\tcd \"$upstream\" &&\n    ++\t\tgit config --unset receive.procReceiveRefs &&\n    + \t\trm -f \"hooks/proc-receive\" &&\n    + \t\tgit update-ref refs/heads/master $A &&\n    + \t\tgit update-ref -d refs/for/master/topic1 &&\n    +\n      ## t/t5411/test-0010-proc-receive-settings.sh (new) ##\n     @@\n     +test_expect_success \"add two receive.procReceiveRefs settings\" '\n7:  861b21d471 = 7:  1a3f58652d doc: add documentation for the proc-receive hook\n\n----\n\nJiang Xin (7):\n  transport: not report a non-head push as a branch\n  receive-pack: add new proc-receive hook\n  New capability \"report-status-v2\" for git-push\n  receive-pack: feed report options to post-receive\n  refs.c: refactor to reuse ref_is_hidden()\n  receive-pack: new config receive.procReceiveRefs\n  doc: add documentation for the proc-receive hook\n\n Documentation/config/receive.txt              |  14 +\n Documentation/githooks.txt                    |  62 +++\n Makefile                                      |   1 +\n builtin/receive-pack.c                        | 461 ++++++++++++++++--\n builtin/send-pack.c                           |  21 +-\n refs.c                                        |  13 +-\n refs.h                                        |   1 +\n remote.c                                      |   5 +-\n remote.h                                      |  16 +-\n send-pack.c                                   |  98 +++-\n t/helper/test-proc-receive.c                  | 175 +++++++\n t/helper/test-tool.c                          |   1 +\n t/helper/test-tool.h                          |   1 +\n t/t5411-proc-receive-hook.sh                  | 114 +++++\n t/t5411/common-functions.sh                   |  56 +++\n t/t5411/once-0010-report-status-v1.sh         | 101 ++++\n t/t5411/test-0000-standard-git-push.sh        |  84 ++++\n .../test-0001-standard-git-push--porcelain.sh |  86 ++++\n t/t5411/test-0002-pre-receive-declined.sh     |  33 ++\n ...st-0003-pre-receive-declined--porcelain.sh |  34 ++\n t/t5411/test-0010-proc-receive-settings.sh    |   7 +\n t/t5411/test-0011-no-hook-error.sh            |  64 +++\n t/t5411/test-0012-no-hook-error--porcelain.sh |  66 +++\n t/t5411/test-0013-bad-protocol.sh             | 217 +++++++++\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 160 ++++++\n t/t5411/test-0020-report-ng.sh                |  67 +++\n t/t5411/test-0021-report-ng--porcelain.sh     |  69 +++\n t/t5411/test-0022-report-unexpect-ref.sh      |  45 ++\n ...est-0023-report-unexpect-ref--porcelain.sh |  46 ++\n t/t5411/test-0024-report-unknown-ref.sh       |  34 ++\n ...test-0025-report-unknown-ref--porcelain.sh |  35 ++\n t/t5411/test-0026-push-options.sh             |  79 +++\n t/t5411/test-0027-push-options--porcelain.sh  |  82 ++++\n t/t5411/test-0030-report-ok.sh                |  35 ++\n t/t5411/test-0031-report-ok--porcelain.sh     |  36 ++\n t/t5411/test-0032-report-with-options.sh      | 256 ++++++++++\n ...est-0033-report-with-options--porcelain.sh | 265 ++++++++++\n t/t5411/test-0034-report-ft.sh                |  44 ++\n t/t5411/test-0035-report-ft--porcelain.sh     |  45 ++\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 169 +++++++\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 172 +++++++\n t/t5411/test-0038-report-mixed-refs.sh        |  89 ++++\n .../test-0039-report-mixed-refs--porcelain.sh |  91 ++++\n t/t5411/test-0040-process-all-refs.sh         | 113 +++++\n .../test-0041-process-all-refs--porcelain.sh  | 114 +++++\n t/t5516-fetch-push.sh                         |   2 +-\n transport-helper.c                            |  53 +-\n transport.c                                   | 121 +++--\n 48 files changed, 3859 insertions(+), 94 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/once-0010-report-status-v1.sh\n create mode 100644 t/t5411/test-0000-standard-git-push.sh\n create mode 100644 t/t5411/test-0001-standard-git-push--porcelain.sh\n create mode 100644 t/t5411/test-0002-pre-receive-declined.sh\n create mode 100644 t/t5411/test-0003-pre-receive-declined--porcelain.sh\n create mode 100644 t/t5411/test-0010-proc-receive-settings.sh\n create mode 100644 t/t5411/test-0011-no-hook-error.sh\n create mode 100644 t/t5411/test-0012-no-hook-error--porcelain.sh\n create mode 100644 t/t5411/test-0013-bad-protocol.sh\n create mode 100644 t/t5411/test-0014-bad-protocol--porcelain.sh\n create mode 100644 t/t5411/test-0020-report-ng.sh\n create mode 100644 t/t5411/test-0021-report-ng--porcelain.sh\n create mode 100644 t/t5411/test-0022-report-unexpect-ref.sh\n create mode 100644 t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n create mode 100644 t/t5411/test-0024-report-unknown-ref.sh\n create mode 100644 t/t5411/test-0025-report-unknown-ref--porcelain.sh\n create mode 100644 t/t5411/test-0026-push-options.sh\n create mode 100644 t/t5411/test-0027-push-options--porcelain.sh\n create mode 100644 t/t5411/test-0030-report-ok.sh\n create mode 100644 t/t5411/test-0031-report-ok--porcelain.sh\n create mode 100644 t/t5411/test-0032-report-with-options.sh\n create mode 100644 t/t5411/test-0033-report-with-options--porcelain.sh\n create mode 100644 t/t5411/test-0034-report-ft.sh\n create mode 100644 t/t5411/test-0035-report-ft--porcelain.sh\n create mode 100644 t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n create mode 100644 t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n create mode 100644 t/t5411/test-0038-report-mixed-refs.sh\n create mode 100644 t/t5411/test-0039-report-mixed-refs--porcelain.sh\n create mode 100644 t/t5411/test-0040-process-all-refs.sh\n create mode 100644 t/t5411/test-0041-process-all-refs--porcelain.sh\n\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"397316","messageId":"20200507161057.12690-5-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqq4kss64h7.fsf@gitster.c.googlers.com","subject":"[PATCH v15 4/7] receive-pack: feed report options to post-receive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-07T16:10:54Z","receivedAt":"2020-05-07T16:11:12Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen commands are fed to the \"post-receive\" hook, report options will\nbe parsed and the real old-oid, new-oid, reference name will feed to\nthe \"post-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c                        | 29 ++++++++++++++++---\n t/t5411/once-0010-report-status-v1.sh         |  3 +-\n t/t5411/test-0032-report-with-options.sh      | 14 ++++-----\n ...est-0033-report-with-options--porcelain.sh | 14 ++++-----\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 11 +++++--\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 11 +++++--\n t/t5411/test-0038-report-mixed-refs.sh        |  2 +-\n .../test-0039-report-mixed-refs--porcelain.sh |  2 +-\n 8 files changed, 59 insertions(+), 27 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex d142bab54f..a1ae6cc17e 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -775,17 +775,38 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n {\n \tstruct receive_hook_feed_state *state = state_;\n \tstruct command *cmd = state->cmd;\n+\tstatic struct ref_push_report_options *options = NULL;\n \n \twhile (cmd &&\n \t       state->skip_broken && (cmd->report.error_message || cmd->did_not_exist))\n \t\tcmd = cmd->next;\n \tif (!cmd)\n \t\treturn -1; /* EOF */\n+\tif (!bufp)\n+\t\treturn 0; /* OK, can feed something. */\n \tstrbuf_reset(&state->buf);\n-\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n-\t\t    oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),\n-\t\t    cmd->ref_name);\n-\tstate->cmd = cmd->next;\n+\tif (!options)\n+\t\toptions = cmd->report.options;\n+\tif (options) {\n+\t\tstruct object_id *old_oid;\n+\t\tstruct object_id *new_oid;\n+\t\tchar *ref_name;\n+\n+\t\told_oid = options->old_oid ? options->old_oid : &cmd->old_oid;\n+\t\tnew_oid = options->new_oid ? options->new_oid : &cmd->new_oid;\n+\t\tref_name = options->ref_name ? options->ref_name : cmd->ref_name;\n+\t\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n+\t\t\t    oid_to_hex(old_oid), oid_to_hex(new_oid),\n+\t\t\t    ref_name);\n+\t\toptions = options->next;\n+\t\tif (!options)\n+\t\t\tstate->cmd = cmd->next;\n+\t} else {\n+\t\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n+\t\t\t    oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),\n+\t\t\t    cmd->ref_name);\n+\t\tstate->cmd = cmd->next;\n+\t}\n \tif (bufp) {\n \t\t*bufp = state->buf.buf;\n \t\t*sizep = state->buf.len;\ndiff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh\nindex 77f84fdc5e..ebabf83937 100644\n--- a/t/t5411/once-0010-report-status-v1.sh\n+++ b/t/t5411/once-0010-report-status-v1.sh\n@@ -68,7 +68,8 @@ test_expect_success \"proc-receive: report status v1\" '\n \tpost-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n \tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n \tpost-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n-\tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2\n+\tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/changes/23/123/1\n+\tpost-receive< <COMMIT-B> <COMMIT-A> refs/for/changes/24/124/2\n \tEOF\n \ttest_cmp expect actual &&\n \ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nindex 3742ed3c3d..b77b78c49f 100644\n--- a/t/t5411/test-0032-report-with-options.sh\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -54,7 +54,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL)\" '\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n@@ -87,7 +87,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n@@ -121,7 +121,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL)\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t <OID-B>..<OID-A> HEAD -> refs/pull/123/head\n \tEOF\n@@ -153,7 +153,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL)\" '\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t <OID-B>..<OID-A> HEAD -> refs/for/master/topic\n \tEOF\n@@ -187,7 +187,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL)\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \tEOF\n@@ -237,9 +237,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n \tremote: proc-receive> option forced-update\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/pull/123/head\n \t * [new reference] HEAD -> refs/for/a/b/c/topic\ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nindex 929c46ccd8..b817d645f1 100644\n--- a/t/t5411/test-0033-report-with-options--porcelain.sh\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -55,7 +55,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL/porcelain)\"\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n@@ -90,7 +90,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n@@ -125,7 +125,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL/\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t     HEAD:refs/pull/123/head    <OID-B>..<OID-A>\n \tDone\n@@ -158,7 +158,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL/porcelain)\"\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t     HEAD:refs/for/master/topic    <OID-B>..<OID-A>\n \tDone\n@@ -193,7 +193,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL/\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \tDone\n@@ -245,9 +245,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porc\n \tremote: proc-receive> option forced-update\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/pull/123/head    [new reference]\n \t*    HEAD:refs/for/a/b/c/topic    [new reference]\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex 64662afdc6..27d58edd14 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -41,7 +41,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \t * [new reference] HEAD -> refs/changes/24/124/1\n@@ -101,7 +103,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/changes/24/124/1\n \t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n@@ -149,7 +153,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/changes/23/123/1\n \t <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2\ndiff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\nindex a371c8d610..2519c1acd8 100644\n--- a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n@@ -41,7 +41,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \t*    HEAD:refs/changes/24/124/1    [new reference]\n@@ -102,7 +104,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/changes/24/124/1    [new reference]\n \t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n@@ -151,7 +155,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porc\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/changes/23/123/1    [new reference]\n \t     HEAD:refs/changes/24/124/2    <OID-A>..<OID-B>\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nindex 54bec4c026..a74a2cb449 100644\n--- a/t/t5411/test-0038-report-mixed-refs.sh\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -53,7 +53,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t <OID-A>..<OID-B> <COMMIT-B> -> master\n \t * [new branch] HEAD -> bar\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nindex f072d85bc4..52bb4e0745 100644\n--- a/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -53,7 +53,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcel\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n \t*    HEAD:refs/heads/bar    [new branch]\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"397318","messageId":"20200507161057.12690-4-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqq4kss64h7.fsf@gitster.c.googlers.com","subject":"[PATCH v15 3/7] New capability \"report-status-v2\" for git-push","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-07T16:10:53Z","receivedAt":"2020-05-07T16:11:12Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThe new introduced \"proc-receive\" hook may handle a command for a\npseudo-reference with a zero-old as its old-oid, while the hook may\ncreate or update a reference with different name, different new-oid,\nand different old-oid (the reference may exist already with a non-zero\nold-oid).  Current \"report-status\" protocol cannot report the status for\nsuch reference rewrite.\n\nAdd new capability \"report-status-v2\" and new report protocol which is\nnot backward compatible for report of git-push.\n\nIf a user pushes to a pseudo-reference \"refs/for/master/topic\", and\n\"receive-pack\" creates two new references \"refs/changes/23/123/1\" and\n\"refs/changes/24/124/1\", for client without the knowledge of\n\"report-status-v2\", \"receive-pack\" will only send \"ok/ng\" directives in\nthe report, such as:\n\n    ok ref/for/master/topic\n\nBut for client which has the knowledge of \"report-status-v2\",\n\"receive-pack\" will use \"option\" directives to report more attributes\nfor the reference given by the above \"ok/ng\" directive.\n\n    ok refs/for/master/topic\n    option refname refs/changes/23/123/1\n    option new-oid <new-oid>\n    ok refs/for/master/topic\n    option refname refs/changes/24/124/1\n    option new-oid <new-oid>\n\nThe client will report two new created references to the end user.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSuggested-by: Jeff King <peff@peff.net>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c                        |  52 +++++++-\n builtin/send-pack.c                           |  21 +++-\n remote.c                                      |   5 +-\n remote.h                                      |   2 +-\n send-pack.c                                   |  98 ++++++++++++---\n t/t5411-proc-receive-hook.sh                  |   6 +\n t/t5411/once-0010-report-status-v1.sh         |  95 ++++++++++++++\n t/t5411/test-0032-report-with-options.sh      |  14 +--\n ...est-0033-report-with-options--porcelain.sh |  14 +--\n ...t-0036-report-multi-rewrite-for-one-ref.sh |  11 +-\n ...rt-multi-rewrite-for-one-ref--porcelain.sh |  11 +-\n t/t5411/test-0038-report-mixed-refs.sh        |   2 +-\n .../test-0039-report-mixed-refs--porcelain.sh |   2 +-\n transport-helper.c                            |  53 +++++++-\n transport.c                                   | 118 +++++++++++++-----\n 15 files changed, 428 insertions(+), 76 deletions(-)\n create mode 100644 t/t5411/once-0010-report-status-v1.sh\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 23206a3c26..d142bab54f 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -56,6 +56,7 @@ static int advertise_push_options;\n static int unpack_limit = 100;\n static off_t max_input_size;\n static int report_status;\n+static int report_status_v2;\n static int use_sideband;\n static int use_atomic;\n static int use_push_options;\n@@ -239,7 +240,7 @@ static void show_ref(const char *path, const struct object_id *oid)\n \t\tstruct strbuf cap = STRBUF_INIT;\n \n \t\tstrbuf_addstr(&cap,\n-\t\t\t      \"report-status delete-refs side-band-64k quiet\");\n+\t\t\t      \"report-status report-status-v2 delete-refs side-band-64k quiet\");\n \t\tif (advertise_atomic_push)\n \t\t\tstrbuf_addstr(&cap, \" atomic\");\n \t\tif (prefer_ofs_delta)\n@@ -1914,6 +1915,8 @@ static struct command *read_head_info(struct packet_reader *reader,\n \t\t\tconst char *feature_list = reader->line + linelen + 1;\n \t\t\tif (parse_feature_request(feature_list, \"report-status\"))\n \t\t\t\treport_status = 1;\n+\t\t\tif (parse_feature_request(feature_list, \"report-status-v2\"))\n+\t\t\t\treport_status_v2 = 1;\n \t\t\tif (parse_feature_request(feature_list, \"side-band-64k\"))\n \t\t\t\tuse_sideband = LARGE_PACKET_MAX;\n \t\t\tif (parse_feature_request(feature_list, \"quiet\"))\n@@ -2225,6 +2228,49 @@ static void report(struct command *commands, const char *unpack_status)\n \tstrbuf_release(&buf);\n }\n \n+static void report_v2(struct command *commands, const char *unpack_status)\n+{\n+\tstruct command *cmd;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ref_push_report_options *options;\n+\n+\tpacket_buf_write(&buf, \"unpack %s\\n\",\n+\t\t\t unpack_status ? unpack_status : \"ok\");\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tint count = 0;\n+\n+\t\tif (!cmd->report.error_message)\n+\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n+\t\t\t\t\t cmd->ref_name);\n+\t\telse\n+\t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n+\t\t\t\t\t cmd->ref_name,\n+\t\t\t\t\t cmd->report.error_message);\n+\t\tfor (options = cmd->report.options; options; options = options->next) {\n+\t\t\tif (count++ > 0)\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s\\n\", cmd->ref_name);\n+\t\t\tif (options->ref_name)\n+\t\t\t\tpacket_buf_write(&buf, \"option refname %s\\n\",\n+\t\t\t\t\t\t options->ref_name);\n+\t\t\tif (options->old_oid)\n+\t\t\t\tpacket_buf_write(&buf, \"option old-oid %s\\n\",\n+\t\t\t\t\t\t oid_to_hex(options->old_oid));\n+\t\t\tif (options->new_oid)\n+\t\t\t\tpacket_buf_write(&buf, \"option new-oid %s\\n\",\n+\t\t\t\t\t\t oid_to_hex(options->new_oid));\n+\t\t\tif (options->forced_update)\n+\t\t\t\tpacket_buf_write(&buf, \"option forced-update\\n\");\n+\t\t}\n+\t}\n+\tpacket_buf_flush(&buf);\n+\n+\tif (use_sideband)\n+\t\tsend_sideband(1, 1, buf.buf, buf.len, use_sideband);\n+\telse\n+\t\twrite_or_die(1, buf.buf, buf.len);\n+\tstrbuf_release(&buf);\n+}\n+\n static int delete_only(struct command *commands)\n {\n \tstruct command *cmd;\n@@ -2333,7 +2379,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\t\t\t &push_options);\n \t\tif (pack_lockfile)\n \t\t\tunlink_or_warn(pack_lockfile);\n-\t\tif (report_status)\n+\t\tif (report_status_v2)\n+\t\t\treport_v2(commands, unpack_status);\n+\t\telse if (report_status)\n \t\t\treport(commands, unpack_status);\n \t\trun_receive_hook(commands, \"post-receive\", 1,\n \t\t\t\t &push_options);\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 2b9610f121..d3a0cd2e91 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -29,10 +29,12 @@ static struct send_pack_args args;\n static void print_helper_status(struct ref *ref)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ref_push_report_options *options;\n \n \tfor (; ref; ref = ref->next) {\n \t\tconst char *msg = NULL;\n \t\tconst char *res;\n+\t\tint count = 0;\n \n \t\tswitch(ref->status) {\n \t\tcase REF_STATUS_NONE:\n@@ -86,14 +88,29 @@ static void print_helper_status(struct ref *ref)\n \n \t\tstrbuf_reset(&buf);\n \t\tstrbuf_addf(&buf, \"%s %s\", res, ref->name);\n-\t\tif (ref->remote_status)\n-\t\t\tmsg = ref->remote_status;\n+\t\tif (ref->report.error_message)\n+\t\t\tmsg = ref->report.error_message;\n \t\tif (msg) {\n \t\t\tstrbuf_addch(&buf, ' ');\n \t\t\tquote_two_c_style(&buf, \"\", msg, 0);\n \t\t}\n \t\tstrbuf_addch(&buf, '\\n');\n \n+\t\tfor (options = ref->report.options; options; options = options->next) {\n+\t\t\tif (count++ > 0)\n+\t\t\t\tstrbuf_addf(&buf, \"ok %s\\n\", ref->name);\n+\t\t\tif (options->ref_name)\n+\t\t\t\tstrbuf_addf(&buf, \"option refname %s\\n\",\n+\t\t\t\t\t\t  options->ref_name);\n+\t\t\tif (options->old_oid)\n+\t\t\t\tstrbuf_addf(&buf, \"option old-oid %s\\n\",\n+\t\t\t\t\t\t  oid_to_hex(options->old_oid));\n+\t\t\tif (options->new_oid)\n+\t\t\t\tstrbuf_addf(&buf, \"option new-oid %s\\n\",\n+\t\t\t\t\t\t  oid_to_hex(options->new_oid));\n+\t\t\tif (options->forced_update)\n+\t\t\t\tstrbuf_addstr(&buf, \"option forced-update\\n\");\n+\t\t}\n \t\twrite_or_die(1, buf.buf, buf.len);\n \t}\n \tstrbuf_release(&buf);\ndiff --git a/remote.c b/remote.c\nindex 534c6426f1..6fd175ba1d 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"config.h\"\n+#include \"connect.h\"\n #include \"remote.h\"\n #include \"refs.h\"\n #include \"refspec.h\"\n@@ -789,7 +790,7 @@ struct ref *copy_ref(const struct ref *ref)\n \tmemcpy(cpy, ref, len);\n \tcpy->next = NULL;\n \tcpy->symref = xstrdup_or_null(ref->symref);\n-\tcpy->remote_status = xstrdup_or_null(ref->remote_status);\n+\tcpy->report.error_message = xstrdup_or_null(ref->report.error_message);\n \tcpy->peer_ref = copy_ref(ref->peer_ref);\n \treturn cpy;\n }\n@@ -811,7 +812,7 @@ void free_one_ref(struct ref *ref)\n \tif (!ref)\n \t\treturn;\n \tfree_one_ref(ref->peer_ref);\n-\tfree(ref->remote_status);\n+\tfree((void *)ref->report.error_message);\n \tfree(ref->symref);\n \tfree(ref);\n }\ndiff --git a/remote.h b/remote.h\nindex 2ed2a16e16..caa3d5c9ba 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -153,7 +153,7 @@ struct ref {\n \t\tREF_STATUS_EXPECTING_REPORT,\n \t\tREF_STATUS_ATOMIC_PUSH_FAILED\n \t} status;\n-\tchar *remote_status;\n+\tstruct ref_push_report report;\n \tstruct ref *peer_ref; /* when renaming */\n \tchar name[FLEX_ARRAY]; /* more */\n };\ndiff --git a/send-pack.c b/send-pack.c\nindex d1b7edc995..d0ebbb0551 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -153,25 +153,80 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n {\n \tstruct ref *hint;\n \tint ret;\n+\tint new_options = 1;\n \n \thint = NULL;\n \tret = receive_unpack_status(reader);\n \twhile (1) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *head;\n \t\tconst char *refname;\n-\t\tchar *msg;\n+\t\tchar *p;\n+\n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n \t\t\tbreak;\n-\t\tif (!starts_with(reader->line, \"ok \") && !starts_with(reader->line, \"ng \")) {\n-\t\t\terror(\"invalid ref status from remote: %s\", reader->line);\n+\t\thead = reader->line;\n+\t\tp = strchr(head, ' ');\n+\t\tif (!p) {\n+\t\t\terror(\"invalid status line from remote: %s\", reader->line);\n \t\t\tret = -1;\n \t\t\tbreak;\n \t\t}\n+\t\t*p++ = '\\0';\n+\n+\t\tif (!strcmp(head, \"option\")) {\n+\t\t\tstruct ref_push_report_options *options;\n+\t\t\tconst char *key, *val;\n+\n+\t\t\tif (!hint) {\n+\t\t\t       if (new_options) {\n+\t\t\t\t\terror(\"'option' without a matching 'ok/ng' directive\");\n+\t\t\t\t\tnew_options = 0;\n+\t\t\t\t}\n+\t\t\t\tret = -1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\toptions = hint->report.options;\n+\t\t\twhile (options && options->next)\n+\t\t\t\toptions = options->next;\n+\t\t\tif (new_options) {\n+\t\t\t\tif (!options) {\n+\t\t\t\t\thint->report.options = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\t\toptions = hint->report.options;\n+\t\t\t\t} else {\n+\t\t\t\t\toptions->next = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\t\toptions = options->next;\n+\t\t\t\t}\n+\t\t\t\tnew_options = 0;\n+\t\t\t}\n+\t\t\tassert(options);\n+\t\t\tkey = p;\n+\t\t\tp = strchr(key, ' ');\n+\t\t\tif (p)\n+\t\t\t\t*p++ = '\\0';\n+\t\t\tval = p;\n+\t\t\tif (!strcmp(key, \"refname\"))\n+\t\t\t\toptions->ref_name = xstrdup_or_null(val);\n+\t\t\telse if (!strcmp(key, \"old-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &old_oid, &val))\n+\t\t\t\toptions->old_oid = oiddup(&old_oid);\n+\t\t\telse if (!strcmp(key, \"new-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &new_oid, &val))\n+\t\t\t\toptions->new_oid = oiddup(&new_oid);\n+\t\t\telse if (!strcmp(key, \"forced-update\"))\n+\t\t\t\toptions->forced_update = 1;\n+\t\t\tcontinue;\n+\t\t}\n \n-\t\trefname = reader->line + 3;\n-\t\tmsg = strchr(refname, ' ');\n-\t\tif (msg)\n-\t\t\t*msg++ = '\\0';\n-\n+\t\tif (strcmp(head, \"ok\") && strcmp(head, \"ng\")) {\n+\t\t\terror(\"invalid ref status from remote: %s\", head);\n+\t\t\tret = -1;\n+\t\t\tbreak;\n+\t\t}\n+\t\trefname = p;\n+\t\tp = strchr(refname, ' ');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n \t\t/* first try searching at our hint, falling back to all refs */\n \t\tif (hint)\n \t\t\thint = find_ref_by_name(hint, refname);\n@@ -182,19 +237,24 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \t\t\t\t\trefname);\n \t\t\tcontinue;\n \t\t}\n-\t\tif (hint->status != REF_STATUS_EXPECTING_REPORT) {\n+\t\tif (hint->status != REF_STATUS_EXPECTING_REPORT &&\n+\t\t    hint->status != REF_STATUS_OK &&\n+\t\t    hint->status != REF_STATUS_REMOTE_REJECT) {\n \t\t\twarning(\"remote reported status on unexpected ref: %s\",\n \t\t\t\t\trefname);\n \t\t\tcontinue;\n \t\t}\n-\n-\t\tif (reader->line[0] == 'o' && reader->line[1] == 'k')\n-\t\t\thint->status = REF_STATUS_OK;\n-\t\telse\n+\t\tif (!strcmp(head, \"ng\")) {\n \t\t\thint->status = REF_STATUS_REMOTE_REJECT;\n-\t\thint->remote_status = xstrdup_or_null(msg);\n-\t\t/* start our next search from the next ref */\n-\t\thint = hint->next;\n+\t\t\tif (p)\n+\t\t\t\thint->report.error_message = xstrdup(p);\n+\t\t\telse\n+\t\t\t\thint->report.error_message = \"failed\";\n+\t\t} else {\n+\t\t\thint->status = REF_STATUS_OK;\n+\t\t\thint->report.message = xstrdup_or_null(p);\n+\t\t}\n+\t\tnew_options = 1;\n \t}\n \treturn ret;\n }\n@@ -371,6 +431,8 @@ int send_pack(struct send_pack_args *args,\n \t/* Does the other end support the reporting? */\n \tif (server_supports(\"report-status\"))\n \t\tstatus_report = 1;\n+\tif (server_supports(\"report-status-v2\"))\n+\t\tstatus_report = 2;\n \tif (server_supports(\"delete-refs\"))\n \t\tallow_deleting_refs = 1;\n \tif (server_supports(\"ofs-delta\"))\n@@ -418,8 +480,10 @@ int send_pack(struct send_pack_args *args,\n \n \tuse_push_options = push_options_supported && args->push_options;\n \n-\tif (status_report)\n+\tif (status_report == 1)\n \t\tstrbuf_addstr(&cap_buf, \" report-status\");\n+\telse if (status_report == 2)\n+\t\tstrbuf_addstr(&cap_buf, \" report-status-v2\");\n \tif (use_sideband)\n \t\tstrbuf_addstr(&cap_buf, \" side-band-64k\");\n \tif (quiet_supported && (args->quiet || !args->progress))\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex 8a1158cc1e..822b63f778 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -78,6 +78,12 @@ run_proc_receive_hook_test() {\n # Initialize the upstream repository and local workbench.\n setup_upstream_and_workbench\n \n+# Load test cases that only need to be executed once.\n+for t in  \"$TEST_DIRECTORY\"/t5411/once-*.sh\n+do\n+\t. \"$t\"\n+done\n+\n # Run test cases for 'proc-receive' hook on local file protocol.\n run_proc_receive_hook_test local\n \ndiff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh\nnew file mode 100644\nindex 0000000000..77f84fdc5e\n--- /dev/null\n+++ b/t/t5411/once-0010-report-status-v1.sh\n@@ -0,0 +1,95 @@\n+test_expect_success \"setup proc-receive hook\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic1\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/for/master/topic2\" \\\n+\t\t-r \"option refname refs/for/changes/23/123/1\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic2\" \\\n+\t\t-r \"option refname refs/for/changes/24/124/2\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\" \\\n+\t\t-r \"ng refs/for/next/topic target branch not exist\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic1(A)  foo(A)  refs/for/next/topic(A)  refs/for/master/topic2(A)\n+test_expect_success \"proc-receive: report status v1\" '\n+\t{\n+\t\tprintf \"%s %s refs/heads/master\\0report-status\\n\" \\\n+\t\t\t$A $B | packetize &&\n+\t\tprintf \"%s %s refs/for/master/topic1\\n\" \\\n+\t\t\t$ZERO_OID $A | packetize &&\n+\t\tprintf \"%s %s refs/heads/foo\\n\" \\\n+\t\t\t$ZERO_OID $A | packetize &&\n+\t\tprintf \"%s %s refs/for/next/topic\\n\" \\\n+\t\t\t$ZERO_OID $A | packetize &&\n+\t\tprintf \"%s %s refs/for/master/topic2\\n\" \\\n+\t\t\t$ZERO_OID $A | packetize &&\n+\t\tprintf 0000 &&\n+\t\tprintf \"\" | git -C \"$upstream\" pack-objects --stdout\n+\t} | git receive-pack \"$upstream\" --stateless-rpc \\\n+\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t# pre-receive hook\n+\tpre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tpre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n+\tpre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tpre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tpre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2\n+\t# proc-receive hook\n+\tproc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n+\tproc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tproc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2\n+\tproc-receive> ok refs/for/master/topic1\n+\tproc-receive> option fall-through\n+\tproc-receive> ok refs/for/master/topic2\n+\tproc-receive> option refname refs/for/changes/23/123/1\n+\tproc-receive> option new-oid <COMMIT-A>\n+\tproc-receive> ok refs/for/master/topic2\n+\tproc-receive> option refname refs/for/changes/24/124/2\n+\tproc-receive> option old-oid <COMMIT-B>\n+\tproc-receive> option new-oid <COMMIT-A>\n+\tproc-receive> option forced-update\n+\tproc-receive> ng refs/for/next/topic target branch not exist\n+\t000eunpack ok\n+\t0019ok refs/heads/master\n+\t001eok refs/for/master/topic1\n+\t0016ok refs/heads/foo\n+\t0033ng refs/for/next/topic target branch not exist\n+\t001eok refs/for/master/topic2\n+\t0000# post-receive hook\n+\tpost-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n+\tpost-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/for/master/topic1\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  refs/for/master/topic1(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\trm -f \"hooks/proc-receive\" &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit update-ref -d refs/for/master/topic1 &&\n+\t\tgit update-ref -d refs/heads/foo\n+\t)\n+'\ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nindex a743aa8018..3742ed3c3d 100644\n--- a/t/t5411/test-0032-report-with-options.sh\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -56,7 +56,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL)\" '\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -89,7 +89,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -123,7 +123,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL)\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-B>..<OID-A> HEAD -> refs/pull/123/head\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -155,7 +155,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL)\" '\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-B>..<OID-A> HEAD -> refs/for/master/topic\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -189,7 +189,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL)\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -241,9 +241,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \t * [new reference] HEAD -> refs/for/a/b/c/topic\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n \ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nindex 8a50d7efe3..929c46ccd8 100644\n--- a/t/t5411/test-0033-report-with-options--porcelain.sh\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -57,7 +57,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL/porcelain)\"\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -92,7 +92,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -127,7 +127,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL/\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/pull/123/head    <OID-B>..<OID-A>\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -160,7 +160,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL/porcelain)\"\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-B>..<OID-A>\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -195,7 +195,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL/\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -249,9 +249,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porc\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/next/topic    [new reference]\n+\t*    HEAD:refs/pull/123/head    [new reference]\n \t*    HEAD:refs/for/a/b/c/topic    [new reference]\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t+    HEAD:refs/pull/124/head    <OID-B>...<OID-A> (forced update)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex 12acf9ea1f..64662afdc6 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -43,7 +43,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/24/124/1\n+\t <OID-A>..<OID-B> HEAD -> refs/changes/25/125/1\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -101,7 +103,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/24/124/1\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n+\t + <OID-B>...<OID-A> HEAD -> refs/changes/25/125/1 (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -147,7 +151,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/23/123/1\n+\t <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\ndiff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\nindex 57dc0d17ac..a371c8d610 100644\n--- a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n@@ -43,7 +43,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n+\t*    HEAD:refs/changes/24/124/1    [new reference]\n+\t     HEAD:refs/changes/25/125/1    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n@@ -102,7 +104,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/changes/24/124/1    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n+\t+    HEAD:refs/changes/25/125/1    <OID-B>...<OID-A> (forced update)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n@@ -149,7 +153,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porc\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/changes/23/123/1    [new reference]\n+\t     HEAD:refs/changes/24/124/2    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nindex 1dd6a97a97..54bec4c026 100644\n--- a/t/t5411/test-0038-report-mixed-refs.sh\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -60,7 +60,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n \t * [new branch] HEAD -> baz\n \t * [new reference] HEAD -> refs/for/next/topic2\n \t * [new branch] HEAD -> foo\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \t ! [remote rejected] HEAD -> refs/for/next/topic1 (fail to call Web API)\n \t ! [remote rejected] HEAD -> refs/for/next/topic3 (proc-receive failed to report status)\n \tEOF\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nindex eaa9c61ae3..f072d85bc4 100644\n--- a/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -60,7 +60,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcel\n \t*    HEAD:refs/heads/baz    [new branch]\n \t*    HEAD:refs/for/next/topic2    [new reference]\n \t*    HEAD:refs/heads/foo    [new branch]\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \t!    HEAD:refs/for/next/topic1    [remote rejected] (fail to call Web API)\n \t!    HEAD:refs/for/next/topic3    [remote rejected] (proc-receive failed to report status)\n \tDone\ndiff --git a/transport-helper.c b/transport-helper.c\nindex a46afcb69d..21d6e47966 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -725,6 +725,49 @@ static int push_update_ref_status(struct strbuf *buf,\n {\n \tchar *refname, *msg;\n \tint status, forced = 0;\n+\tstatic struct ref *hint = NULL;\n+\tstatic int new_options = 1;\n+\n+\tif (starts_with(buf->buf, \"option \")) {\n+\t\tstruct ref_push_report_options *options;\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *key, *val;\n+\t\tchar *p;\n+\n+\t\tif (!hint)\n+\t\t\tdie(_(\"'option' without a matching 'ok/error' directive\"));\n+\t\toptions = hint->report.options;\n+\t\twhile (options && options->next)\n+\t\t\toptions = options->next;\n+\t\tif (new_options) {\n+\t\t\tif (!options) {\n+\t\t\t\thint->report.options = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\toptions = hint->report.options;\n+\t\t\t} else {\n+\t\t\t\toptions->next = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\toptions = options->next;\n+\t\t\t}\n+\t\t\tnew_options = 0;\n+\t\t}\n+\t\tassert(options);\n+\t\tkey = buf->buf + 7;\n+\t\tp = strchr(key, ' ');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n+\t\tval = p;\n+\t\tif (!strcmp(key, \"refname\"))\n+\t\t\toptions->ref_name = xstrdup_or_null(val);\n+\t\telse if (!strcmp(key, \"old-oid\") && val &&\n+\t\t\t !parse_oid_hex(val, &old_oid, &val))\n+\t\t\toptions->old_oid = oiddup(&old_oid);\n+\t\telse if (!strcmp(key, \"new-oid\") && val &&\n+\t\t\t !parse_oid_hex(val, &new_oid, &val))\n+\t\t\toptions->new_oid = oiddup(&new_oid);\n+\t\telse if (!strcmp(key, \"forced-update\"))\n+\t\t\toptions->forced_update = 1;\n+\t\t/* Not update remote namespace again. */\n+\t\treturn 1;\n+\t}\n \n \tif (starts_with(buf->buf, \"ok \")) {\n \t\tstatus = REF_STATUS_OK;\n@@ -787,8 +830,11 @@ static int push_update_ref_status(struct strbuf *buf,\n \t\t*ref = find_ref_by_name(remote_refs, refname);\n \tif (!*ref) {\n \t\twarning(_(\"helper reported unexpected status of %s\"), refname);\n+\t\thint = NULL;\n \t\treturn 1;\n \t}\n+\thint = *ref;\n+\tnew_options = 1;\n \n \tif ((*ref)->status != REF_STATUS_NONE) {\n \t\t/*\n@@ -801,7 +847,12 @@ static int push_update_ref_status(struct strbuf *buf,\n \n \t(*ref)->status = status;\n \t(*ref)->forced_update |= forced;\n-\t(*ref)->remote_status = msg;\n+\tif (msg) {\n+\t\tif (status == REF_STATUS_OK)\n+\t\t\t(*ref)->report.message = msg;\n+\t\telse\n+\t\t\t(*ref)->report.error_message = msg;\n+\t}\n \treturn !(status == REF_STATUS_OK);\n }\n \ndiff --git a/transport.c b/transport.c\nindex 26fefd13c1..2e2b7de036 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -461,13 +461,21 @@ void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int v\n \n static void print_ref_status(char flag, const char *summary,\n \t\t\t     struct ref *to, struct ref *from, const char *msg,\n+\t\t\t     struct ref_push_report_options *options,\n \t\t\t     int porcelain, int summary_width)\n {\n+\tchar *to_name;\n+\n+\tif (options && options->ref_name)\n+\t\tto_name = options->ref_name;\n+\telse\n+\t\tto_name = to->name;\n+\n \tif (porcelain) {\n \t\tif (from)\n-\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to_name);\n \t\telse\n-\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to_name);\n \t\tif (msg)\n \t\t\tfprintf(stdout, \"%s (%s)\\n\", summary, msg);\n \t\telse\n@@ -481,9 +489,11 @@ static void print_ref_status(char flag, const char *summary,\n \t\tfprintf(stderr, \" %s%c %-*s%s \", red, flag, summary_width,\n \t\t\tsummary, reset);\n \t\tif (from)\n-\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(to->name));\n+\t\t\tfprintf(stderr, \"%s -> %s\",\n+\t\t\t\tprettify_refname(from->name),\n+\t\t\t\tprettify_refname(to_name));\n \t\telse\n-\t\t\tfputs(prettify_refname(to->name), stderr);\n+\t\t\tfputs(prettify_refname(to_name), stderr);\n \t\tif (msg) {\n \t\t\tfputs(\" (\", stderr);\n \t\t\tfputs(msg, stderr);\n@@ -493,27 +503,52 @@ static void print_ref_status(char flag, const char *summary,\n \t}\n }\n \n-static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_width)\n+static void print_ok_ref_status(struct ref *ref,\n+\t\t\t\tstruct ref_push_report_options *options,\n+\t\t\t\tint porcelain, int summary_width)\n {\n+\tstruct object_id *old_oid;\n+\tstruct object_id *new_oid;\n+\tconst char *ref_name;\n+\tint forced_update;\n+\n+\tif (options && options->old_oid)\n+\t\told_oid = options->old_oid;\n+\telse\n+\t\told_oid = &ref->old_oid;\n+\tif (options && options->new_oid)\n+\t\tnew_oid = options->new_oid;\n+\telse\n+\t\tnew_oid = &ref->new_oid;\n+\tif (options && options->forced_update)\n+\t\tforced_update = options->forced_update;\n+\telse\n+\t\tforced_update = ref->forced_update;\n+\tif (options && options->ref_name)\n+\t\tref_name = options->ref_name;\n+\telse\n+\t\tref_name = ref->name;\n+\n \tif (ref->deletion)\n \t\tprint_ref_status('-', \"[deleted]\", ref, NULL, NULL,\n-\t\t\t\t porcelain, summary_width);\n-\telse if (is_null_oid(&ref->old_oid))\n+\t\t\t\t options, porcelain, summary_width);\n+\telse if (is_null_oid(old_oid))\n \t\tprint_ref_status('*',\n-\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t (starts_with(ref_name, \"refs/tags/\")\n \t\t\t\t  ? \"[new tag]\"\n-\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t  : (starts_with(ref_name, \"refs/heads/\")\n \t\t\t\t     ? \"[new branch]\"\n \t\t\t\t     : \"[new reference]\")),\n-\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t ref, ref->peer_ref, NULL,\n+\t\t\t\t options, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n \t\tconst char *msg;\n \n-\t\tstrbuf_add_unique_abbrev(&quickref, &ref->old_oid,\n+\t\tstrbuf_add_unique_abbrev(&quickref, old_oid,\n \t\t\t\t\t DEFAULT_ABBREV);\n-\t\tif (ref->forced_update) {\n+\t\tif (forced_update) {\n \t\t\tstrbuf_addstr(&quickref, \"...\");\n \t\t\ttype = '+';\n \t\t\tmsg = \"forced update\";\n@@ -522,17 +557,18 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\t\ttype = ' ';\n \t\t\tmsg = NULL;\n \t\t}\n-\t\tstrbuf_add_unique_abbrev(&quickref, &ref->new_oid,\n+\t\tstrbuf_add_unique_abbrev(&quickref, new_oid,\n \t\t\t\t\t DEFAULT_ABBREV);\n \n \t\tprint_ref_status(type, quickref.buf, ref, ref->peer_ref, msg,\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t options, porcelain, summary_width);\n \t\tstrbuf_release(&quickref);\n \t}\n }\n \n-static int print_one_push_status(struct ref *ref, const char *dest, int count,\n-\t\t\t\t int porcelain, int summary_width)\n+static int _print_one_push_status(struct ref *ref, const char *dest, int count,\n+\t\t\t\t  struct ref_push_report_options *options,\n+\t\t\t\t  int porcelain, int summary_width)\n {\n \tif (!count) {\n \t\tchar *url = transport_anonymize_url(dest);\n@@ -543,65 +579,89 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count,\n \tswitch(ref->status) {\n \tcase REF_STATUS_NONE:\n \t\tprint_ref_status('X', \"[no match]\", ref, NULL, NULL,\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NODELETE:\n \t\tprint_ref_status('!', \"[rejected]\", ref, NULL,\n \t\t\t\t \"remote does not support deleting refs\",\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_UPTODATE:\n \t\tprint_ref_status('=', \"[up to date]\", ref,\n-\t\t\t\t ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t ref->peer_ref, NULL,\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NONFASTFORWARD:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"non-fast-forward\", porcelain, summary_width);\n+\t\t\t\t \"non-fast-forward\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"already exists\", porcelain, summary_width);\n+\t\t\t\t \"already exists\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_FETCH_FIRST:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"fetch first\", porcelain, summary_width);\n+\t\t\t\t \"fetch first\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NEEDS_FORCE:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"needs force\", porcelain, summary_width);\n+\t\t\t\t \"needs force\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_STALE:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"stale info\", porcelain, summary_width);\n+\t\t\t\t \"stale info\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_SHALLOW:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n \t\t\t\t \"new shallow roots not allowed\",\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REMOTE_REJECT:\n \t\tprint_ref_status('!', \"[remote rejected]\", ref,\n \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n-\t\t\t\t ref->remote_status, porcelain, summary_width);\n+\t\t\t\t ref->report.error_message,\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_EXPECTING_REPORT:\n \t\tprint_ref_status('!', \"[remote failure]\", ref,\n \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n \t\t\t\t \"remote failed to report status\",\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_ATOMIC_PUSH_FAILED:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"atomic push failed\", porcelain, summary_width);\n+\t\t\t\t \"atomic push failed\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_OK:\n-\t\tprint_ok_ref_status(ref, porcelain, summary_width);\n+\t\tprint_ok_ref_status(ref, options, porcelain, summary_width);\n \t\tbreak;\n \t}\n \n \treturn 1;\n }\n \n+static int print_one_push_status(struct ref *ref, const char *dest, int count,\n+\t\t\t\t int porcelain, int summary_width)\n+{\n+\tstruct ref_push_report_options *options;\n+\tint n = 0;\n+\n+\tif (!ref->report.options)\n+\t\treturn _print_one_push_status(ref, dest, count,\n+\t\t\t\t\t      NULL, porcelain, summary_width);\n+\n+\tfor (options = ref->report.options; options; options = options->next)\n+\t\t_print_one_push_status(ref, dest, count + n++,\n+\t\t\t\t       options, porcelain, summary_width);\n+\treturn n;\n+}\n+\n static int measure_abbrev(const struct object_id *oid, int sofar)\n {\n \tchar hex[GIT_MAX_HEXSZ + 1];\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"397317","messageId":"20200507161057.12690-6-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqq4kss64h7.fsf@gitster.c.googlers.com","subject":"[PATCH v15 5/7] refs.c: refactor to reuse ref_is_hidden()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-07T16:10:55Z","receivedAt":"2020-05-07T16:11:13Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd new function `ref_is_matched()` to reuse `ref_is_hidden()`. Will use\nthis function for `receive-pack` to check commands with specific\nprefixes.\n\nTest case t5512 covered this change.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n refs.c | 13 +++++++++----\n refs.h |  1 +\n 2 files changed, 10 insertions(+), 4 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 224ff66c7b..76a91ac0f6 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1344,14 +1344,14 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti\n \treturn 0;\n }\n \n-int ref_is_hidden(const char *refname, const char *refname_full)\n+int ref_matches(struct string_list *match_refs, const char *refname, const char *refname_full)\n {\n \tint i;\n \n-\tif (!hide_refs)\n+\tif (!match_refs)\n \t\treturn 0;\n-\tfor (i = hide_refs->nr - 1; i >= 0; i--) {\n-\t\tconst char *match = hide_refs->items[i].string;\n+\tfor (i = match_refs->nr - 1; i >= 0; i--) {\n+\t\tconst char *match = match_refs->items[i].string;\n \t\tconst char *subject;\n \t\tint neg = 0;\n \t\tconst char *p;\n@@ -1377,6 +1377,11 @@ int ref_is_hidden(const char *refname, const char *refname_full)\n \treturn 0;\n }\n \n+int ref_is_hidden(const char *refname, const char *refname_full)\n+{\n+\treturn ref_matches(hide_refs, refname, refname_full);\n+}\n+\n const char *find_descendant_ref(const char *dirname,\n \t\t\t\tconst struct string_list *extras,\n \t\t\t\tconst struct string_list *skip)\ndiff --git a/refs.h b/refs.h\nindex a92d2c74c8..9907dd12b0 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -727,6 +727,7 @@ int parse_hide_refs_config(const char *var, const char *value, const char *);\n  * parameter always points to the full ref name.\n  */\n int ref_is_hidden(const char *, const char *);\n+int ref_matches(struct string_list *, const char *, const char *);\n \n enum ref_type {\n \tREF_TYPE_PER_WORKTREE,\t  /* refs inside refs/ but not shared       */\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"397321","messageId":"20200507161057.12690-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqq4kss64h7.fsf@gitster.c.googlers.com","subject":"[PATCH v15 2/7] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-07T16:10:52Z","receivedAt":"2020-05-07T16:11:14Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nGit calls an internal `execute_commands` function to handle commands\nsent from client to `git-receive-pack`.  Regardless of what references\nthe user pushes, git creates or updates the corresponding references if\nthe user has write-permission.  A contributor who has no\nwrite-permission, cannot push to the repository directly.  So, the\ncontributor has to write commits to an alternate location, and sends\npull request by emails or by other ways.  We call this workflow as a\ndistributed workflow.\n\nIt would be more convenient to work in a centralized workflow like what\nGerrit provided for some cases.  For example, a read-only user who\ncannot push to a branch directly can run the following `git push`\ncommand to push commits to a pseudo reference (has a prefix \"refs/for/\",\nnot \"refs/heads/\") to create a code review.\n\n    git push origin \\\n        HEAD:refs/for/<branch-name>/<session>\n\nThe `<branch-name>` in the above example can be as simple as \"master\",\nor a more complicated branch name like \"foo/bar\".  The `<session>` in\nthe above example command can be the local branch name of the client\nside, such as \"my/topic\".\n\nWe cannot implement a centralized workflow elegantly by using\n\"pre-receive\" + \"post-receive\", because Git will call the internal\nfunction \"execute_commands\" to create references (even the special\npseudo reference) between these two hooks.  Even though we can delete\nthe temporarily created pseudo reference via the \"post-receive\" hook,\nhaving a temporary reference is not safe for concurrent pushes.\n\nSo, add a filter and a new handler to support this kind of workflow.\nThe filter will check the prefix of the reference name, and if the\ncommand has a special reference name, the filter will turn a specific\nfield (`run_proc_receive`) on for the command.  Commands with this filed\nturned on will be executed by a new handler (an hook named\n\"proc-receive\") instead of the internal `execute_commands` function.\nWe can use this \"proc-receive\" command to create pull requests or send\nemails for code review.\n\nSuggested by Junio, this \"proc-receive\" hook reads the commands,\npush-options (optional), and send result using a protocol in pkt-line\nformat.  In the following example, The letter \"S\" stands for\n\"receive-pack\" and letter \"H\" stands for the hook.\n\n    # Version and features negotiation.\n    S: PKT-LINE(version=1\\0push-options atomic...)\n    S: flush-pkt\n    H: PKT-LINE(version=1\\0push-options...)\n    H: flush-pkt\n\n    # Send commands from server to the hook.\n    S: PKT-LINE(<old-oid> <new-oid> <ref>)\n    S: ... ...\n    S: flush-pkt\n    # Send push-options only if the 'push-options' feature is enabled.\n    S: PKT-LINE(push-option)\n    S: ... ...\n    S: flush-pkt\n\n    # Receive result from the hook.\n    # OK, run this command successfully.\n    H: PKT-LINE(ok <ref>)\n    # NO, I reject it.\n    H: PKT-LINE(ng <ref> <reason>)\n    # Fall through, let 'receive-pack' to execute it.\n    H: PKT-LINE(ok <ref>)\n    H: PKT-LINE(option fall-through)\n    # OK, but has an alternate reference.  The alternate reference name\n    # and other status can be given in options\n    H: PKT-LINE(ok <ref>)\n    H: PKT-LINE(option refname <refname>)\n    H: PKT-LINE(option old-oid <old-oid>)\n    H: PKT-LINE(option new-oid <new-oid>)\n    H: PKT-LINE(option forced-update)\n    H: ... ...\n    H: flush-pkt\n\nAfter receiving a command, the hook will execute the command, and may\ncreate/update different reference.  For example, a command for a pseudo\nreference \"refs/for/master/topic\" may create/update different reference\nsuch as \"refs/pull/123/head\".  The alternate reference name and other\nstatus are given in option lines.\n\nThe list of commands returned from \"proc-receive\" will replace the\nrelevant commands that are sent from user to \"receive-pack\", and\n\"receive-pack\" will continue to run the \"execute_commands\" function and\nother routines.  Finally, the result of the execution of these commands\nwill be reported to end user.\n\nThe reporting function from \"receive-pack\" to \"send-pack\" will be\nextended in latter commit just like what the \"proc-receive\" hook reports\nto \"receive-pack\".\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Makefile                                      |   1 +\n builtin/receive-pack.c                        | 353 ++++++++++++++++--\n remote.h                                      |  14 +\n t/helper/test-proc-receive.c                  | 175 +++++++++\n t/helper/test-tool.c                          |   1 +\n t/helper/test-tool.h                          |   1 +\n t/t5411/test-0011-no-hook-error.sh            |  64 ++++\n t/t5411/test-0012-no-hook-error--porcelain.sh |  66 ++++\n t/t5411/test-0013-bad-protocol.sh             | 217 +++++++++++\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 160 ++++++++\n t/t5411/test-0020-report-ng.sh                |  67 ++++\n t/t5411/test-0021-report-ng--porcelain.sh     |  69 ++++\n t/t5411/test-0022-report-unexpect-ref.sh      |  45 +++\n ...est-0023-report-unexpect-ref--porcelain.sh |  46 +++\n t/t5411/test-0024-report-unknown-ref.sh       |  34 ++\n ...test-0025-report-unknown-ref--porcelain.sh |  35 ++\n t/t5411/test-0026-push-options.sh             |  79 ++++\n t/t5411/test-0027-push-options--porcelain.sh  |  82 ++++\n t/t5411/test-0030-report-ok.sh                |  35 ++\n t/t5411/test-0031-report-ok--porcelain.sh     |  36 ++\n t/t5411/test-0032-report-with-options.sh      | 256 +++++++++++++\n ...est-0033-report-with-options--porcelain.sh | 265 +++++++++++++\n t/t5411/test-0034-report-ft.sh                |  44 +++\n t/t5411/test-0035-report-ft--porcelain.sh     |  45 +++\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 159 ++++++++\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 162 ++++++++\n t/t5411/test-0038-report-mixed-refs.sh        |  89 +++++\n .../test-0039-report-mixed-refs--porcelain.sh |  91 +++++\n 28 files changed, 2659 insertions(+), 32 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100644 t/t5411/test-0011-no-hook-error.sh\n create mode 100644 t/t5411/test-0012-no-hook-error--porcelain.sh\n create mode 100644 t/t5411/test-0013-bad-protocol.sh\n create mode 100644 t/t5411/test-0014-bad-protocol--porcelain.sh\n create mode 100644 t/t5411/test-0020-report-ng.sh\n create mode 100644 t/t5411/test-0021-report-ng--porcelain.sh\n create mode 100644 t/t5411/test-0022-report-unexpect-ref.sh\n create mode 100644 t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n create mode 100644 t/t5411/test-0024-report-unknown-ref.sh\n create mode 100644 t/t5411/test-0025-report-unknown-ref--porcelain.sh\n create mode 100644 t/t5411/test-0026-push-options.sh\n create mode 100644 t/t5411/test-0027-push-options--porcelain.sh\n create mode 100644 t/t5411/test-0030-report-ok.sh\n create mode 100644 t/t5411/test-0031-report-ok--porcelain.sh\n create mode 100644 t/t5411/test-0032-report-with-options.sh\n create mode 100644 t/t5411/test-0033-report-with-options--porcelain.sh\n create mode 100644 t/t5411/test-0034-report-ft.sh\n create mode 100644 t/t5411/test-0035-report-ft--porcelain.sh\n create mode 100644 t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n create mode 100644 t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n create mode 100644 t/t5411/test-0038-report-mixed-refs.sh\n create mode 100644 t/t5411/test-0039-report-mixed-refs--porcelain.sh\n\ndiff --git a/Makefile b/Makefile\nindex 3d3a39fc19..607aeabeee 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -722,6 +722,7 @@ TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\n+TEST_BUILTINS_OBJS += test-proc-receive.o\n TEST_BUILTINS_OBJS += test-progress.o\n TEST_BUILTINS_OBJS += test-reach.o\n TEST_BUILTINS_OBJS += test-read-cache.o\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex a00f91c1a0..23206a3c26 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -308,11 +308,14 @@ static void write_head_info(void)\n \tpacket_flush(1);\n }\n \n+#define RUN_PROC_RECEIVE_SCHEDULED\t1\n+#define RUN_PROC_RECEIVE_RETURNED\t2\n struct command {\n \tstruct command *next;\n-\tconst char *error_string;\n+\tstruct ref_push_report report;\n \tunsigned int skip_update:1,\n-\t\t     did_not_exist:1;\n+\t\t     did_not_exist:1,\n+\t\t     run_proc_receive:2;\n \tint index;\n \tstruct object_id old_oid;\n \tstruct object_id new_oid;\n@@ -773,7 +776,7 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n \tstruct command *cmd = state->cmd;\n \n \twhile (cmd &&\n-\t       state->skip_broken && (cmd->error_string || cmd->did_not_exist))\n+\t       state->skip_broken && (cmd->report.error_message || cmd->did_not_exist))\n \t\tcmd = cmd->next;\n \tif (!cmd)\n \t\treturn -1; /* EOF */\n@@ -838,6 +841,268 @@ static int run_update_hook(struct command *cmd)\n \treturn finish_command(&proc);\n }\n \n+static struct command *find_command_by_refname(const struct command *list,\n+\t\t\t\t\t       const char *refname)\n+{\n+\tfor (; list; list = list->next)\n+\t\tif (!strcmp(list->ref_name, refname))\n+\t\t\treturn (struct command *)list;\n+\treturn NULL;\n+}\n+\n+static int read_proc_receive_report(struct packet_reader *reader,\n+\t\t\t\t    struct command *commands,\n+\t\t\t\t    struct strbuf *errmsg)\n+{\n+\tstruct command *cmd;\n+\tstruct command *hint = NULL;\n+\tint code = 0;\n+\tint new_options = 1;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *head;\n+\t\tconst char *refname;\n+\t\tchar *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\thead = reader->line;\n+\t\tp = strchr(head, ' ');\n+\t\tif (!p) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported incomplete status line: '%s'\\n\", head);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\t*p++ = '\\0';\n+\t\tif (!strcmp(head, \"option\")) {\n+\t\t\tstruct ref_push_report_options *options;\n+\t\t\tconst char *key, *val;\n+\n+\t\t\tif (!hint) {\n+\t\t\t       if (new_options) {\n+\t\t\t\t\tstrbuf_addstr(errmsg, \"proc-receive reported 'option' without a matching 'ok/ng' directive\\n\");\n+\t\t\t\t\tnew_options = 0;\n+\t\t\t\t}\n+\t\t\t\tcode = -1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\toptions = hint->report.options;\n+\t\t\twhile (options && options->next)\n+\t\t\t\toptions = options->next;\n+\t\t\tif (new_options) {\n+\t\t\t\tif (!options) {\n+\t\t\t\t\thint->report.options = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\t\toptions = hint->report.options;\n+\t\t\t\t} else {\n+\t\t\t\t\toptions->next = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\t\toptions = options->next;\n+\t\t\t\t}\n+\t\t\t\tnew_options = 0;\n+\t\t\t}\n+\t\t\tassert(options);\n+\t\t\tkey = p;\n+\t\t\tp = strchr(key, ' ');\n+\t\t\tif (p)\n+\t\t\t\t*p++ = '\\0';\n+\t\t\tval = p;\n+\t\t\tif (!strcmp(key, \"refname\"))\n+\t\t\t\toptions->ref_name = xstrdup_or_null(val);\n+\t\t\telse if (!strcmp(key, \"old-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &old_oid, &val))\n+\t\t\t\toptions->old_oid = oiddup(&old_oid);\n+\t\t\telse if (!strcmp(key, \"new-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &new_oid, &val))\n+\t\t\t\toptions->new_oid = oiddup(&new_oid);\n+\t\t\telse if (!strcmp(key, \"forced-update\"))\n+\t\t\t\toptions->forced_update = 1;\n+\t\t\telse if (!strcmp(key, \"fall-through\"))\n+\t\t\t\t/* Fall through, let 'receive-pack' to execute it. */\n+\t\t\t\thint->run_proc_receive = 0;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\trefname = p;\n+\t\tp = strchr(refname, ' ');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n+\t\tif (strcmp(head, \"ok\") && strcmp(head, \"ng\")) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported bad status '%s' on ref '%s'\\n\",\n+\t\t\t\t    head, refname);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/* first try searching at our hint, falling back to all refs */\n+\t\tif (hint)\n+\t\t\thint = find_command_by_refname(hint, refname);\n+\t\tif (!hint)\n+\t\t\thint = find_command_by_refname(commands, refname);\n+\t\tif (!hint) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unknown ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!hint->run_proc_receive) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unexpected ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(head, \"ng\")) {\n+\t\t\tif (p)\n+\t\t\t\thint->report.error_message = xstrdup(p);\n+\t\t\telse\n+\t\t\t\thint->report.error_message = \"failed\";\n+\t\t\tcode = -1;\n+\t\t}\n+\t\tif (hint->run_proc_receive)\n+\t\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n+\t\tnew_options = 1;\n+\t}\n+\n+\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\tif (cmd->run_proc_receive && !cmd->report.error_message &&\n+\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {\n+\t\t    cmd->report.error_message = \"proc-receive failed to report status\";\n+\t\t    code = -1;\n+\t\t}\n+\treturn code;\n+}\n+\n+static int run_proc_receive_hook(struct command *commands,\n+\t\t\t\t const struct string_list *push_options)\n+{\n+\tstruct child_process proc = CHILD_PROCESS_INIT;\n+\tstruct async muxer;\n+\tstruct command *cmd;\n+\tconst char *argv[2];\n+\tstruct packet_reader reader;\n+\tstruct strbuf cap = STRBUF_INIT;\n+\tstruct strbuf errmsg = STRBUF_INIT;\n+\tint hook_use_push_options = 0;\n+\tint version = 0;\n+\tint code;\n+\n+\targv[0] = find_hook(\"proc-receive\");\n+\tif (!argv[0]) {\n+\t\trp_error(\"cannot find hook 'proc-receive'\");\n+\t\treturn -1;\n+\t}\n+\targv[1] = NULL;\n+\n+\tproc.argv = argv;\n+\tproc.in = -1;\n+\tproc.out = -1;\n+\tproc.trace2_hook_name = \"proc-receive\";\n+\n+\tif (use_sideband) {\n+\t\tmemset(&muxer, 0, sizeof(muxer));\n+\t\tmuxer.proc = copy_to_sideband;\n+\t\tmuxer.in = -1;\n+\t\tcode = start_async(&muxer);\n+\t\tif (code)\n+\t\t\treturn code;\n+\t\tproc.err = muxer.in;\n+\t} else {\n+\t\tproc.err = 0;\n+\t}\n+\n+\tcode = start_command(&proc);\n+\tif (code) {\n+\t\tif (use_sideband)\n+\t\t\tfinish_async(&muxer);\n+\t\treturn code;\n+\t}\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\n+\t/* Version negotiaton */\n+\tpacket_reader_init(&reader, proc.out, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_GENTLE_ON_EOF);\n+\tif (use_atomic)\n+\t\tstrbuf_addstr(&cap, \" atomic\");\n+\tif (use_push_options)\n+\t\tstrbuf_addstr(&cap, \" push-options\");\n+\tif (cap.len) {\n+\t\tpacket_write_fmt(proc.in, \"version=1%c%s\\n\", '\\0', cap.buf + 1);\n+\t\tstrbuf_release(&cap);\n+\t} else {\n+\t\tpacket_write_fmt(proc.in, \"version=1\\n\");\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader.pktlen > 8 && starts_with(reader.line, \"version=\")) {\n+\t\t\tversion = atoi(reader.line + 8);\n+\t\t\tlinelen = strlen(reader.line);\n+\t\t\tif (linelen < reader.pktlen) {\n+\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\thook_use_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (version != 1) {\n+\t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n+\t\t\t    version);\n+\t\tcode = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\t/* Send commands */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->report.error_message)\n+\t\t\tcontinue;\n+\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n+\t\t\t\t oid_to_hex(&cmd->old_oid),\n+\t\t\t\t oid_to_hex(&cmd->new_oid),\n+\t\t\t\t cmd->ref_name);\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\t/* Send push options */\n+\tif (hook_use_push_options) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tfor_each_string_list_item(item, push_options)\n+\t\t\tpacket_write_fmt(proc.in, \"%s\", item->string);\n+\t\tpacket_flush(proc.in);\n+\t}\n+\n+\t/* Read result from proc-receive */\n+\tcode = read_proc_receive_report(&reader, commands, &errmsg);\n+\n+cleanup:\n+\tclose(proc.in);\n+\tclose(proc.out);\n+\tif (use_sideband)\n+\t\tfinish_async(&muxer);\n+\tif (finish_command(&proc))\n+\t\tcode = -1;\n+\tif (errmsg.len >0) {\n+\t\tchar *p = errmsg.buf;\n+\n+\t\tp += errmsg.len - 1;\n+\t\tif (*p == '\\n')\n+\t\t\t*p = '\\0';\n+\t\trp_error(\"%s\", errmsg.buf);\n+\t\tstrbuf_release(&errmsg);\n+\t}\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn code;\n+}\n+\n static char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n@@ -1201,7 +1466,7 @@ static void run_update_post_hook(struct command *commands)\n \t\treturn;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (cmd->error_string || cmd->did_not_exist)\n+\t\tif (cmd->report.error_message || cmd->did_not_exist)\n \t\t\tcontinue;\n \t\tif (!proc.args.argc)\n \t\t\targv_array_push(&proc.args, hook);\n@@ -1235,7 +1500,7 @@ static void check_aliased_update_internal(struct command *cmd,\n \tif (!dst_name) {\n \t\trp_error(\"refusing update to broken symref '%s'\", cmd->ref_name);\n \t\tcmd->skip_update = 1;\n-\t\tcmd->error_string = \"broken symref\";\n+\t\tcmd->report.error_message = \"broken symref\";\n \t\treturn;\n \t}\n \tdst_name = strip_namespace(dst_name);\n@@ -1262,7 +1527,7 @@ static void check_aliased_update_internal(struct command *cmd,\n \t\t find_unique_abbrev(&dst_cmd->old_oid, DEFAULT_ABBREV),\n \t\t find_unique_abbrev(&dst_cmd->new_oid, DEFAULT_ABBREV));\n \n-\tcmd->error_string = dst_cmd->error_string =\n+\tcmd->report.error_message = dst_cmd->report.error_message =\n \t\t\"inconsistent aliased update\";\n }\n \n@@ -1291,7 +1556,7 @@ static void check_aliased_updates(struct command *commands)\n \tstring_list_sort(&ref_list);\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!cmd->error_string)\n+\t\tif (!cmd->report.error_message)\n \t\t\tcheck_aliased_update(cmd, &ref_list);\n \t}\n \n@@ -1328,7 +1593,7 @@ static void set_connectivity_errors(struct command *commands,\n \t\t\t\t     &opt))\n \t\t\tcontinue;\n \n-\t\tcmd->error_string = \"missing necessary objects\";\n+\t\tcmd->report.error_message = \"missing necessary objects\";\n \t}\n }\n \n@@ -1367,7 +1632,7 @@ static void reject_updates_to_hidden(struct command *commands)\n \tprefix_len = refname_full.len;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (cmd->error_string)\n+\t\tif (cmd->report.error_message)\n \t\t\tcontinue;\n \n \t\tstrbuf_setlen(&refname_full, prefix_len);\n@@ -1376,9 +1641,9 @@ static void reject_updates_to_hidden(struct command *commands)\n \t\tif (!ref_is_hidden(cmd->ref_name, refname_full.buf))\n \t\t\tcontinue;\n \t\tif (is_null_oid(&cmd->new_oid))\n-\t\t\tcmd->error_string = \"deny deleting a hidden ref\";\n+\t\t\tcmd->report.error_message = \"deny deleting a hidden ref\";\n \t\telse\n-\t\t\tcmd->error_string = \"deny updating a hidden ref\";\n+\t\t\tcmd->report.error_message = \"deny updating a hidden ref\";\n \t}\n \n \tstrbuf_release(&refname_full);\n@@ -1386,7 +1651,7 @@ static void reject_updates_to_hidden(struct command *commands)\n \n static int should_process_cmd(struct command *cmd)\n {\n-\treturn !cmd->error_string && !cmd->skip_update;\n+\treturn !cmd->report.error_message && !cmd->skip_update;\n }\n \n static void warn_if_skipped_connectivity_check(struct command *commands,\n@@ -1413,24 +1678,24 @@ static void execute_commands_non_atomic(struct command *commands,\n \tstruct strbuf err = STRBUF_INIT;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\ttransaction = ref_transaction_begin(&err);\n \t\tif (!transaction) {\n \t\t\trp_error(\"%s\", err.buf);\n \t\t\tstrbuf_reset(&err);\n-\t\t\tcmd->error_string = \"transaction failed to start\";\n+\t\t\tcmd->report.error_message = \"transaction failed to start\";\n \t\t\tcontinue;\n \t\t}\n \n-\t\tcmd->error_string = update(cmd, si);\n+\t\tcmd->report.error_message = update(cmd, si);\n \n-\t\tif (!cmd->error_string\n+\t\tif (!cmd->report.error_message\n \t\t    && ref_transaction_commit(transaction, &err)) {\n \t\t\trp_error(\"%s\", err.buf);\n \t\t\tstrbuf_reset(&err);\n-\t\t\tcmd->error_string = \"failed to update ref\";\n+\t\t\tcmd->report.error_message = \"failed to update ref\";\n \t\t}\n \t\tref_transaction_free(transaction);\n \t}\n@@ -1453,12 +1718,12 @@ static void execute_commands_atomic(struct command *commands,\n \t}\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n-\t\tcmd->error_string = update(cmd, si);\n+\t\tcmd->report.error_message = update(cmd, si);\n \n-\t\tif (cmd->error_string)\n+\t\tif (cmd->report.error_message)\n \t\t\tgoto failure;\n \t}\n \n@@ -1471,8 +1736,8 @@ static void execute_commands_atomic(struct command *commands,\n \n failure:\n \tfor (cmd = commands; cmd; cmd = cmd->next)\n-\t\tif (!cmd->error_string)\n-\t\t\tcmd->error_string = reported_error;\n+\t\tif (!cmd->report.error_message)\n+\t\t\tcmd->report.error_message = reported_error;\n \n cleanup:\n \tref_transaction_free(transaction);\n@@ -1489,10 +1754,11 @@ static void execute_commands(struct command *commands,\n \tstruct iterate_data data;\n \tstruct async muxer;\n \tint err_fd = 0;\n+\tint run_proc_receive = 0;\n \n \tif (unpacker_error) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n-\t\t\tcmd->error_string = \"unpacker error\";\n+\t\t\tcmd->report.error_message = \"unpacker error\";\n \t\treturn;\n \t}\n \n@@ -1518,10 +1784,25 @@ static void execute_commands(struct command *commands,\n \n \treject_updates_to_hidden(commands);\n \n+\t/*\n+\t * Try to find commands that have special prefix in their reference names,\n+\t * and mark them to run an external \"proc-receive\" hook later.\n+\t */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!should_process_cmd(cmd))\n+\t\t\tcontinue;\n+\n+\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n+\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n+\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\trun_proc_receive = 1;\n+\t\t}\n+\t}\n+\n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\t\tif (!cmd->error_string)\n-\t\t\t\tcmd->error_string = \"pre-receive hook declined\";\n+\t\t\tif (!cmd->report.error_message)\n+\t\t\t\tcmd->report.error_message = \"pre-receive hook declined\";\n \t\t}\n \t\treturn;\n \t}\n@@ -1532,8 +1813,8 @@ static void execute_commands(struct command *commands,\n \t */\n \tif (tmp_objdir_migrate(tmp_objdir) < 0) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\t\tif (!cmd->error_string)\n-\t\t\t\tcmd->error_string = \"unable to migrate objects to permanent storage\";\n+\t\t\tif (!cmd->report.error_message)\n+\t\t\t\tcmd->report.error_message = \"unable to migrate objects to permanent storage\";\n \t\t}\n \t\treturn;\n \t}\n@@ -1544,6 +1825,14 @@ static void execute_commands(struct command *commands,\n \tfree(head_name_to_free);\n \thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, NULL, NULL);\n \n+\tif (run_proc_receive &&\n+\t    run_proc_receive_hook(commands, push_options))\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tif (!cmd->report.error_message &&\n+\t\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED) &&\n+\t\t\t    (cmd->run_proc_receive || use_atomic))\n+\t\t\t\tcmd->report.error_message = \"fail to run proc-receive hook\";\n+\n \tif (use_atomic)\n \t\texecute_commands_atomic(commands, si);\n \telse\n@@ -1905,7 +2194,7 @@ static void update_shallow_info(struct command *commands,\n \t\tif (is_null_oid(&cmd->new_oid))\n \t\t\tcontinue;\n \t\tif (ref_status[cmd->index]) {\n-\t\t\tcmd->error_string = \"shallow update not allowed\";\n+\t\t\tcmd->report.error_message = \"shallow update not allowed\";\n \t\t\tcmd->skip_update = 1;\n \t\t}\n \t}\n@@ -1920,12 +2209,12 @@ static void report(struct command *commands, const char *unpack_status)\n \tpacket_buf_write(&buf, \"unpack %s\\n\",\n \t\t\t unpack_status ? unpack_status : \"ok\");\n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!cmd->error_string)\n+\t\tif (!cmd->report.error_message)\n \t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n-\t\t\t\t\t cmd->ref_name);\n+\t\t\t\t\t\t cmd->ref_name);\n \t\telse\n \t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n-\t\t\t\t\t cmd->ref_name, cmd->error_string);\n+\t\t\t\t\t cmd->ref_name, cmd->report.error_message);\n \t}\n \tpacket_buf_flush(&buf);\n \n@@ -2029,7 +2318,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tif (!check_cert_push_options(&push_options)) {\n \t\t\tstruct command *cmd;\n \t\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n-\t\t\t\tcmd->error_string = \"inconsistent push options\";\n+\t\t\t\tcmd->report.error_message = \"inconsistent push options\";\n \t\t}\n \n \t\tprepare_shallow_info(&si, &shallow);\ndiff --git a/remote.h b/remote.h\nindex 11d8719b58..2ed2a16e16 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -93,6 +93,20 @@ int for_each_remote(each_remote_fn fn, void *priv);\n \n int remote_has_url(struct remote *remote, const char *url);\n \n+struct ref_push_report_options {\n+\tchar *ref_name;\n+\tstruct object_id *old_oid;\n+\tstruct object_id *new_oid;\n+\tunsigned int forced_update:1;\n+\tstruct ref_push_report_options *next;\n+};\n+\n+struct ref_push_report {\n+\tconst char *message;\n+\tconst char *error_message;\n+\tstruct ref_push_report_options *options;\n+};\n+\n struct ref {\n \tstruct ref *next;\n \tstruct object_id old_oid;\ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nnew file mode 100644\nindex 0000000000..6ace1863b0\n--- /dev/null\n+++ b/t/helper/test-proc-receive.c\n@@ -0,0 +1,175 @@\n+#include \"cache.h\"\n+#include \"connect.h\"\n+#include \"parse-options.h\"\n+#include \"pkt-line.h\"\n+#include \"sigchain.h\"\n+#include \"string-list.h\"\n+#include \"test-tool.h\"\n+\n+static const char *proc_receive_usage[] = {\n+\t\"test-tool proc-receive [<options>...]\",\n+\tNULL\n+};\n+\n+static int die_version = 0;\n+static int die_readline = 0;\n+static int no_push_options = 0;\n+static int use_atomic = 0;\n+static int use_push_options = 0;\n+static int verbose = 0;\n+static int version = 1;\n+static struct string_list returns = STRING_LIST_INIT_NODUP;\n+\n+struct command {\n+\tstruct command *next;\n+\tconst char *error_string;\n+\tunsigned int skip_update:1,\n+\t\t     did_not_exist:1;\n+\tint index;\n+\tstruct object_id old_oid;\n+\tstruct object_id new_oid;\n+\tchar ref_name[FLEX_ARRAY]; /* more */\n+};\n+\n+static void proc_receive_verison(struct packet_reader *reader) {\n+\tint server_version = 0;\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader->pktlen > 8 && starts_with(reader->line, \"version=\")) {\n+\t\t\tserver_version = atoi(reader->line+8);\n+\t\t\tlinelen = strlen(reader->line);\n+\t\t\tif (linelen < reader->pktlen) {\n+\t\t\t\tconst char *feature_list = reader->line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"atomic\"))\n+\t\t\t\t\tuse_atomic= 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tuse_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (server_version != 1 || die_version)\n+\t\tdie(\"bad protocol version: %d\", server_version);\n+\n+\tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n+\t\t\t version, '\\0',\n+\t\t\t use_push_options && !no_push_options ? \"push-options\": \"\");\n+\tpacket_flush(1);\n+}\n+\n+static void proc_receive_read_commands(struct packet_reader *reader,\n+\t\t\t\t       struct command **commands)\n+{\n+\tstruct command **tail = commands;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tstruct command *cmd;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    die_readline)\n+\t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n+\t\t\t    reader->line);\n+\t\trefname = p;\n+\t\tFLEX_ALLOC_STR(cmd, ref_name, refname);\n+\t\toidcpy(&cmd->old_oid, &old_oid);\n+\t\toidcpy(&cmd->new_oid, &new_oid);\n+\n+\t\t*tail = cmd;\n+\t\ttail = &cmd->next;\n+\t}\n+}\n+\n+static void proc_receive_read_push_options(struct packet_reader *reader,\n+\t\t\t\t\t   struct string_list *options)\n+{\n+\n+\tif (no_push_options || !use_push_options)\n+\t       return;\n+\n+\twhile (1) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tstring_list_append(options, reader->line);\n+\t}\n+}\n+\n+int cmd__proc_receive(int argc, const char **argv)\n+{\n+\tstruct packet_reader reader;\n+\tstruct command *commands = NULL;\n+\tstruct string_list push_options = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"no-push-options\", &no_push_options,\n+\t\t\t \"disable push options\"),\n+\t\tOPT_BOOL(0, \"die-version\", &die_version,\n+\t\t\t \"die during version negotiation\"),\n+\t\tOPT_BOOL(0, \"die-readline\", &die_readline,\n+\t\t\t \"die when readline\"),\n+\t\tOPT_STRING_LIST('r', \"return\", &returns, \"old/new/ref/status/msg\",\n+\t\t\t\t\"return of results\"),\n+\t\tOPT__VERBOSE(&verbose, \"be verbose\"),\n+\t\tOPT_INTEGER('V', \"version\", &version,\n+\t\t\t    \"use this protocol version number\"),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, \"test-tools\", options, proc_receive_usage, 0);\n+\tif (argc > 0)\n+\t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n+\n+\tpacket_reader_init(&reader, 0, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\tproc_receive_verison(&reader);\n+\tproc_receive_read_commands(&reader, &commands);\n+\tproc_receive_read_push_options(&reader, &push_options);\n+\n+\tif (verbose) {\n+\t\tstruct command *cmd;\n+\n+\t\tif (use_push_options || use_atomic)\n+\t\t\tfprintf(stderr, \"proc-receive:%s%s\\n\",\n+\t\t\t\tuse_atomic? \" atomic\": \"\",\n+\t\t\t\tuse_push_options ? \" push_options\": \"\");\n+\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tfprintf(stderr, \"proc-receive< %s %s %s\\n\",\n+\t\t\t\toid_to_hex(&cmd->old_oid),\n+\t\t\t\toid_to_hex(&cmd->new_oid),\n+\t\t\t\tcmd->ref_name);\n+\n+\t\tif (push_options.nr > 0)\n+\t\t\tfor_each_string_list_item(item, &push_options)\n+\t\t\t\tfprintf(stderr, \"proc-receive< %s\\n\", item->string);\n+\n+\t\tif (returns.nr)\n+\t\t\tfor_each_string_list_item(item, &returns)\n+\t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n+\t}\n+\n+\tif (returns.nr)\n+\t\tfor_each_string_list_item(item, &returns)\n+\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n+\tpacket_flush(1);\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 590b2efca7..a0d3966b29 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -46,6 +46,7 @@ static struct test_cmd cmds[] = {\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\n+\t{ \"proc-receive\", cmd__proc_receive},\n \t{ \"progress\", cmd__progress },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex ddc8e990e9..07034d3f38 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -35,6 +35,7 @@ int cmd__parse_pathspec_file(int argc, const char** argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\n+int cmd__proc_receive(int argc, const char **argv);\n int cmd__progress(int argc, const char **argv);\n int cmd__reach(int argc, const char **argv);\n int cmd__read_cache(int argc, const char **argv);\ndiff --git a/t/t5411/test-0011-no-hook-error.sh b/t/t5411/test-0011-no-hook-error.sh\nnew file mode 100644\nindex 0000000000..bb6ec92a92\n--- /dev/null\n+++ b/t/t5411/test-0011-no-hook-error.sh\n@@ -0,0 +1,64 @@\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\t$B:master \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] <COMMIT-B> -> master (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0012-no-hook-error--porcelain.sh b/t/t5411/test-0012-no-hook-error--porcelain.sh\nnew file mode 100644\nindex 0000000000..de3a2cebef\n--- /dev/null\n+++ b/t/t5411/test-0012-no-hook-error--porcelain.sh\n@@ -0,0 +1,66 @@\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain --atomic origin \\\n+\t\t$B:master \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t!    <COMMIT-B>:refs/heads/master    [remote rejected] (fail to run proc-receive hook)\n+\t!    HEAD:refs/heads/next    [remote rejected] (fail to run proc-receive hook)\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh\nnew file mode 100644\nindex 0000000000..c5fe4cb37b\n--- /dev/null\n+++ b/t/t5411/test-0013-bad-protocol.sh\n@@ -0,0 +1,217 @@\n+test_expect_success \"setup proc-receive hook (unknown version, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\t# Check status report for git-push\n+\tsed -n \\\n+\t\t-e \"/^To / { p; n; p; }\" \\\n+\t\t<actual >actual-report &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual-report &&\n+\n+\t# Check error message from \"receive-pack\", but ignore unstable fatal error\n+\t# message (\"remote: fatal: the remote end hung up unexpectedly\") which\n+\t# is different from the remote HTTP server with different locale settings.\n+\tgrep \"^remote: error:\" <actual >actual-error &&\n+\tcat >expect <<-EOF &&\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tEOF\n+\ttest_cmp expect actual-error &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-version, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-version\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-version, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: fatal: bad protocol version: 1\n+\tremote: error: proc-receive version \"0\" is not supported\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-readline, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-readline\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-readline, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\tgrep \"remote: fatal: protocol error: expected \\\"old new ref\\\", got \\\"<ZERO-OID> <COMMIT-A> refs/for/master/topic\\\"\" actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok\n+\tremote: error: proc-receive reported incomplete status line: \"ok\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"xx refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> xx refs/for/master/topic\n+\tremote: error: proc-receive reported bad status \"xx\" on ref \"refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0014-bad-protocol--porcelain.sh b/t/t5411/test-0014-bad-protocol--porcelain.sh\nnew file mode 100644\nindex 0000000000..e56a76a5ac\n--- /dev/null\n+++ b/t/t5411/test-0014-bad-protocol--porcelain.sh\n@@ -0,0 +1,160 @@\n+test_expect_success \"setup proc-receive hook (unknown version, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\t# Check status report for git-push\n+\tsed -n \\\n+\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n+\t\t<actual >actual-report &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual-report &&\n+\n+\t# Check error message from \"receive-pack\", but ignore unstable fatal error\n+\t# message (\"remote: fatal: the remote end hung up unexpectedly\") which\n+\t# is different from the remote HTTP server with different locale settings.\n+\tgrep \"^remote: error:\" <actual >actual-error &&\n+\tcat >expect <<-EOF &&\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tEOF\n+\ttest_cmp expect actual-error &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok\n+\tremote: error: proc-receive reported incomplete status line: \"ok\"\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"xx refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> xx refs/for/master/topic\n+\tremote: error: proc-receive reported bad status \"xx\" on ref \"refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0020-report-ng.sh b/t/t5411/test-0020-report-ng.sh\nnew file mode 100644\nindex 0000000000..f726b7ca9c\n--- /dev/null\n+++ b/t/t5411/test-0020-report-ng.sh\n@@ -0,0 +1,67 @@\n+test_expect_success \"setup proc-receive hook (ng, no message, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic error msg\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0021-report-ng--porcelain.sh b/t/t5411/test-0021-report-ng--porcelain.sh\nnew file mode 100644\nindex 0000000000..2defe93bc9\n--- /dev/null\n+++ b/t/t5411/test-0021-report-ng--porcelain.sh\n@@ -0,0 +1,69 @@\n+test_expect_success \"setup proc-receive hook (ng, no message, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (failed)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic error msg\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (error msg)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0022-report-unexpect-ref.sh b/t/t5411/test-0022-report-unexpect-ref.sh\nnew file mode 100644\nindex 0000000000..92a415b929\n--- /dev/null\n+++ b/t/t5411/test-0022-report-unexpect-ref.sh\n@@ -0,0 +1,45 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: error: proc-receive reported status on unexpected ref: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\ndiff --git a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\nnew file mode 100644\nindex 0000000000..d3a5b421ee\n--- /dev/null\n+++ b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n@@ -0,0 +1,46 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: error: proc-receive reported status on unexpected ref: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\ndiff --git a/t/t5411/test-0024-report-unknown-ref.sh b/t/t5411/test-0024-report-unknown-ref.sh\nnew file mode 100644\nindex 0000000000..c3946f329a\n--- /dev/null\n+++ b/t/t5411/test-0024-report-unknown-ref.sh\n@@ -0,0 +1,34 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: report unknown reference ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0025-report-unknown-ref--porcelain.sh b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\nnew file mode 100644\nindex 0000000000..b2dd4a4173\n--- /dev/null\n+++ b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\n@@ -0,0 +1,35 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: report unknown reference ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/a/b/c/my/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh\nnew file mode 100644\nindex 0000000000..d0c4da8b23\n--- /dev/null\n+++ b/t/t5411/test-0026-push-options.sh\n@@ -0,0 +1,79 @@\n+test_expect_success \"setup proc-receive hook and disable push-options ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions false &&\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"proc-receive: not support push options ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n+\t\tactual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"proc-receive: push with options ($PROTOCOL)\" '\n+\tgit -C workbench push \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\ndiff --git a/t/t5411/test-0027-push-options--porcelain.sh b/t/t5411/test-0027-push-options--porcelain.sh\nnew file mode 100644\nindex 0000000000..c3461232b8\n--- /dev/null\n+++ b/t/t5411/test-0027-push-options--porcelain.sh\n@@ -0,0 +1,82 @@\n+test_expect_success \"setup proc-receive hook and disable push-options ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions false &&\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"proc-receive: not support push options ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t--porcelain \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n+\t\tactual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"proc-receive: push with options ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push \\\n+\t\t--porcelain \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\ndiff --git a/t/t5411/test-0030-report-ok.sh b/t/t5411/test-0030-report-ok.sh\nnew file mode 100644\nindex 0000000000..44c99d3831\n--- /dev/null\n+++ b/t/t5411/test-0030-report-ok.sh\n@@ -0,0 +1,35 @@\n+test_expect_success \"setup proc-receive hook (ok, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0031-report-ok--porcelain.sh b/t/t5411/test-0031-report-ok--porcelain.sh\nnew file mode 100644\nindex 0000000000..58877ef914\n--- /dev/null\n+++ b/t/t5411/test-0031-report-ok--porcelain.sh\n@@ -0,0 +1,36 @@\n+test_expect_success \"setup proc-receive hook (ok, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nnew file mode 100644\nindex 0000000000..a743aa8018\n--- /dev/null\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -0,0 +1,256 @@\n+test_expect_success \"setup proc-receive hook (option without matching ok, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option without matching ok ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: error: proc-receive reported \"option\" without a matching \"ok/ng\" directive\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and forced-update, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and forced-update ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and old-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid and new-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (report with multiple rewrites, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/a/b/c/topic\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option forced-update\" \\\n+\t\t-r \"option new-oid $A\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/for/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/a/b/c/topic\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option forced-update\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/for/a/b/c/topic\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nnew file mode 100644\nindex 0000000000..8a50d7efe3\n--- /dev/null\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -0,0 +1,265 @@\n+test_expect_success \"setup proc-receive hook (option without matching ok, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option without matching ok ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: error: proc-receive reported \"option\" without a matching \"ok/ng\" directive\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and forced-update, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and forced-update ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and old-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid and new-oid, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (report with multiple rewrites, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/a/b/c/topic\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option forced-update\" \\\n+\t\t-r \"option new-oid $A\"\n+\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/for/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/a/b/c/topic\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option forced-update\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/next/topic    [new reference]\n+\t*    HEAD:refs/for/a/b/c/topic    [new reference]\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0034-report-ft.sh b/t/t5411/test-0034-report-ft.sh\nnew file mode 100644\nindex 0000000000..aca2b0676c\n--- /dev/null\n+++ b/t/t5411/test-0034-report-ft.sh\n@@ -0,0 +1,44 @@\n+test_expect_success \"setup proc-receive hook (ft, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option fall-through\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(B)\n+test_expect_success \"proc-receive: fall throught, let receive-pack to execute ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option fall-through\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] <COMMIT-B> -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/for/master/topic\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             refs/for/master/topic(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/for/master/topic\n+'\ndiff --git a/t/t5411/test-0035-report-ft--porcelain.sh b/t/t5411/test-0035-report-ft--porcelain.sh\nnew file mode 100644\nindex 0000000000..114a14b544\n--- /dev/null\n+++ b/t/t5411/test-0035-report-ft--porcelain.sh\n@@ -0,0 +1,45 @@\n+test_expect_success \"setup proc-receive hook (fall-through, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option fall-through\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(B)\n+test_expect_success \"proc-receive: fall throught, let receive-pack to execute ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\t$B:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option fall-through\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    <COMMIT-B>:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/for/master/topic\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             refs/for/master/topic(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/for/master/topic\n+'\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nnew file mode 100644\nindex 0000000000..12acf9ea1f\n--- /dev/null\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -0,0 +1,159 @@\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/23/123/1\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/2\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/23/123/1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/2\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\nnew file mode 100644\nindex 0000000000..57dc0d17ac\n--- /dev/null\n+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n@@ -0,0 +1,162 @@\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/23/123/1\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/2\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/23/123/1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/2\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nnew file mode 100644\nindex 0000000000..1dd6a97a97\n--- /dev/null\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -0,0 +1,89 @@\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/next/topic2\" \\\n+\t\t-r \"ng refs/for/next/topic1 fail to call Web API\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic2 \\\n+\t\tHEAD:refs/for/next/topic1 \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic3 \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n+\tremote: proc-receive> ok refs/for/next/topic2\n+\tremote: proc-receive> ng refs/for/next/topic1 fail to call Web API\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t * [new branch] HEAD -> bar\n+\t * [new branch] HEAD -> baz\n+\t * [new reference] HEAD -> refs/for/next/topic2\n+\t * [new branch] HEAD -> foo\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\t ! [remote rejected] HEAD -> refs/for/next/topic1 (fail to call Web API)\n+\t ! [remote rejected] HEAD -> refs/for/next/topic3 (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit update-ref -d refs/heads/foo &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+'\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nnew file mode 100644\nindex 0000000000..eaa9c61ae3\n--- /dev/null\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -0,0 +1,91 @@\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/next/topic2\" \\\n+\t\t-r \"ng refs/for/next/topic1 fail to call Web API\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic2 \\\n+\t\tHEAD:refs/for/next/topic1 \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic3 \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n+\tremote: proc-receive> ok refs/for/next/topic2\n+\tremote: proc-receive> ng refs/for/next/topic1 fail to call Web API\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n+\t*    HEAD:refs/heads/bar    [new branch]\n+\t*    HEAD:refs/heads/baz    [new branch]\n+\t*    HEAD:refs/for/next/topic2    [new reference]\n+\t*    HEAD:refs/heads/foo    [new branch]\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\t!    HEAD:refs/for/next/topic1    [remote rejected] (fail to call Web API)\n+\t!    HEAD:refs/for/next/topic3    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit update-ref -d refs/heads/foo &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+\n+'\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"397319","messageId":"20200507161057.12690-7-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqq4kss64h7.fsf@gitster.c.googlers.com","subject":"[PATCH v15 6/7] receive-pack: new config receive.procReceiveRefs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-07T16:10:56Z","receivedAt":"2020-05-07T16:11:17Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd a new multi-valued config variable \"receive.procReceiveRefs\"\nfor `receive-pack` command, like the follows:\n\n    git config --system --add receive.procReceiveRefs refs/for\n    git config --system --add receive.procReceiveRefs refs/drafts\n\nIf the specific prefix strings match the reference names of the commands\nwhich are sent from git client to `receive-pack`, these commands will be\nexecuted by an external hook (named \"proc-receive\"), instead of the\ninternal `execute_commands` function.\n\nFor example, if it is set to \"refs/for\", pushing to a reference such as\n\"refs/for/master\" will not create or update reference \"refs/for/master\",\nbut may create or update a pull request directly by running the hook\n\"proc-receive\".\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/config/receive.txt              |  14 +++\n builtin/receive-pack.c                        |  43 +++++--\n t/t5411/once-0010-report-status-v1.sh         |   5 +\n t/t5411/test-0010-proc-receive-settings.sh    |   7 ++\n t/t5411/test-0040-process-all-refs.sh         | 113 +++++++++++++++++\n .../test-0041-process-all-refs--porcelain.sh  | 114 ++++++++++++++++++\n 6 files changed, 289 insertions(+), 7 deletions(-)\n create mode 100644 t/t5411/test-0010-proc-receive-settings.sh\n create mode 100644 t/t5411/test-0040-process-all-refs.sh\n create mode 100644 t/t5411/test-0041-process-all-refs--porcelain.sh\n\ndiff --git a/Documentation/config/receive.txt b/Documentation/config/receive.txt\nindex 65f78aac37..e7b967feeb 100644\n--- a/Documentation/config/receive.txt\n+++ b/Documentation/config/receive.txt\n@@ -114,6 +114,20 @@ receive.hideRefs::\n \tAn attempt to update or delete a hidden ref by `git push` is\n \trejected.\n \n+receive.procReceiveRefs::\n+\tThis is a multi-valued variable that defines reference prefixes\n+\tto match the commands in `receive-pack`.  Commands matching the\n+\tprefixes will be executed by an external hook \"proc-receive\",\n+\tinstead of the internal `execute_commands` function.  If this\n+\tvariable is not defined, the \"proc-receive\" hook will never be\n+\tused, and all commands will be executed by the internal\n+\t`execute_commands` function.\n+\n+\tFor example, if this variable is set to \"refs/for\", pushing to\n+\treference such as \"refs/for/master\" will not create or update a\n+\treference named \"refs/for/master\", but may create or update a\n+\tpull request directly by running the hook \"proc-receive\".\n+\n receive.updateServerInfo::\n \tIf set to true, git-receive-pack will run git-update-server-info\n \tafter receiving data from git-push and updating refs.\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex a1ae6cc17e..fc6684c1a0 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -77,6 +77,7 @@ static struct object_id push_cert_oid;\n static struct signature_check sigcheck;\n static const char *push_cert_nonce;\n static const char *cert_nonce_seed;\n+static struct string_list proc_receive_refs;\n \n static const char *NONCE_UNSOLICITED = \"UNSOLICITED\";\n static const char *NONCE_BAD = \"BAD\";\n@@ -229,6 +230,20 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (strcmp(var, \"receive.procreceiverefs\") == 0) {\n+\t\tchar *prefix;\n+\t\tint len;\n+\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tprefix = xstrdup(value);\n+\t\tlen = strlen(prefix);\n+\t\twhile (len && prefix[len - 1] == '/')\n+\t\t\tprefix[--len] = '\\0';\n+\t\tstring_list_append(&proc_receive_refs, prefix);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -1810,15 +1825,26 @@ static void execute_commands(struct command *commands,\n \t * Try to find commands that have special prefix in their reference names,\n \t * and mark them to run an external \"proc-receive\" hook later.\n \t */\n-\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n-\t\t\tcontinue;\n+\tif (proc_receive_refs.nr > 0) {\n+\t\tstruct strbuf refname_full = STRBUF_INIT;\n+\t\tsize_t prefix_len;\n+\n+\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n+\t\tprefix_len = refname_full.len;\n \n-\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n-\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n-\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n-\t\t\trun_proc_receive = 1;\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tif (!should_process_cmd(cmd))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_setlen(&refname_full, prefix_len);\n+\t\t\tstrbuf_addstr(&refname_full, cmd->ref_name);\n+\t\t\tif (ref_matches(&proc_receive_refs, cmd->ref_name, refname_full.buf)) {\n+\t\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\t\trun_proc_receive = 1;\n+\t\t\t}\n \t\t}\n+\n+\t\tstrbuf_release(&refname_full);\n \t}\n \n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n@@ -2319,6 +2345,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n+\tstring_list_init(&proc_receive_refs, 0);\n+\n \tpacket_trace_identity(\"receive-pack\");\n \n \targc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);\n@@ -2436,5 +2464,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \toid_array_clear(&shallow);\n \toid_array_clear(&ref);\n \tfree((void *)push_cert_nonce);\n+\tstring_list_clear(&proc_receive_refs, 0);\n \treturn 0;\n }\ndiff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh\nindex ebabf83937..884dc21ad2 100644\n--- a/t/t5411/once-0010-report-status-v1.sh\n+++ b/t/t5411/once-0010-report-status-v1.sh\n@@ -1,3 +1,7 @@\n+test_expect_success \"setup receive.procReceiveRefs\" '\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs/for\n+'\n+\n test_expect_success \"setup proc-receive hook\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n@@ -88,6 +92,7 @@ test_expect_success \"proc-receive: report status v1\" '\n test_expect_success \"cleanup\" '\n \t(\n \t\tcd \"$upstream\" &&\n+\t\tgit config --unset receive.procReceiveRefs &&\n \t\trm -f \"hooks/proc-receive\" &&\n \t\tgit update-ref refs/heads/master $A &&\n \t\tgit update-ref -d refs/for/master/topic1 &&\ndiff --git a/t/t5411/test-0010-proc-receive-settings.sh b/t/t5411/test-0010-proc-receive-settings.sh\nnew file mode 100644\nindex 0000000000..a36809927b\n--- /dev/null\n+++ b/t/t5411/test-0010-proc-receive-settings.sh\n@@ -0,0 +1,7 @@\n+test_expect_success \"add two receive.procReceiveRefs settings\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit config --add receive.procReceiveRefs refs/for &&\n+\t\tgit config --add receive.procReceiveRefs refs/review/\n+\t)\n+'\ndiff --git a/t/t5411/test-0040-process-all-refs.sh b/t/t5411/test-0040-process-all-refs.sh\nnew file mode 100644\nindex 0000000000..b07c999f53\n--- /dev/null\n+++ b/t/t5411/test-0040-process-all-refs.sh\n@@ -0,0 +1,113 @@\n+test_expect_success \"config receive.procReceiveRefs = refs ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config --unset-all receive.procReceiveRefs &&\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup upstream branches ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $B &&\n+\t\tgit update-ref refs/heads/foo $A &&\n+\t\tgit update-ref refs/heads/bar $A &&\n+\t\tgit update-ref refs/heads/baz $A\n+\t)\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/foo\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/bar\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"proc-receive: process all refs ($PROTOCOL)\" '\n+\tgit -C workbench push -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/foo\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/bar\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> bar\n+\t - [deleted] foo\n+\t + <OID-B>...<OID-A> HEAD -> master (forced update)\n+\t <OID-A>..<OID-B> HEAD -> refs/pull/123/head\n+\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             bar(A)  baz(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+'\ndiff --git a/t/t5411/test-0041-process-all-refs--porcelain.sh b/t/t5411/test-0041-process-all-refs--porcelain.sh\nnew file mode 100644\nindex 0000000000..7092db0a06\n--- /dev/null\n+++ b/t/t5411/test-0041-process-all-refs--porcelain.sh\n@@ -0,0 +1,114 @@\n+test_expect_success \"config receive.procReceiveRefs = refs ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config --unset-all receive.procReceiveRefs &&\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup upstream branches ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $B &&\n+\t\tgit update-ref refs/heads/foo $A &&\n+\t\tgit update-ref refs/heads/bar $A &&\n+\t\tgit update-ref refs/heads/baz $A\n+\t)\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/foo\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/bar\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"proc-receive: process all refs ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/foo\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/bar\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/bar    <OID-A>..<OID-B>\n+\t-    :refs/heads/foo    [deleted]\n+\t+    HEAD:refs/heads/master    <OID-B>...<OID-A> (forced update)\n+\t     HEAD:refs/pull/123/head    <OID-A>..<OID-B>\n+\t+    HEAD:refs/pull/124/head    <OID-B>...<OID-A> (forced update)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             bar(A)  baz(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+'\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"397320","messageId":"20200507161057.12690-8-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqq4kss64h7.fsf@gitster.c.googlers.com","subject":"[PATCH v15 7/7] doc: add documentation for the proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-07T16:10:57Z","receivedAt":"2020-05-07T16:11:21Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd documentation for the new \"proc-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/githooks.txt | 62 ++++++++++++++++++++++++++++++++++++++\n 1 file changed, 62 insertions(+)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 81f2a87e88..d66154f606 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -333,6 +333,68 @@ The default 'update' hook, when enabled--and with\n `hooks.allowunannotated` config option unset or set to false--prevents\n unannotated tags to be pushed.\n \n+[[proc-receive]]\n+proc-receive\n+~~~~~~~~~~~~\n+\n+This hook is invoked by linkgit:git-receive-pack[1].  If the server has\n+set the multi-valued config variable `receive.procReceiveRefs`, and the\n+commands sent to 'receive-pack' have matching reference names, these\n+commands will be executed by this hook, instead of by the internal\n+`execute_commands()` function.  This hook is responsible for updating\n+the relevant references and reporting the results back to 'receive-pack'.\n+\n+This hook executes once for the receive operation.  It takes no\n+arguments, but uses a pkt-line format protocol to communicate with\n+'receive-pack' to read commands, push-options and send results.  In the\n+following example for the protocol, the letter 'S' stands for\n+'receive-pack' and the letter 'H' stands for this hook.\n+\n+    # Version and features negotiation.\n+    S: PKT-LINE(version=1\\0push-options atomic...)\n+    S: flush-pkt\n+    H: PKT-LINE(version=1\\0push-options...)\n+    H: flush-pkt\n+\n+    # Send commands from server to the hook.\n+    S: PKT-LINE(<old-oid> <new-oid> <ref>)\n+    S: ... ...\n+    S: flush-pkt\n+    # Send push-options only if the 'push-options' feature is enabled.\n+    S: PKT-LINE(push-option)\n+    S: ... ...\n+    S: flush-pkt\n+\n+    # Receive result from the hook.\n+    # OK, run this command successfully.\n+    H: PKT-LINE(ok <ref>)\n+    # NO, I reject it.\n+    H: PKT-LINE(ng <ref> <reason>)\n+    # Fall through, let 'receive-pack' to execute it.\n+    H: PKT-LINE(ok <ref>)\n+    H: PKT-LINE(option fall-through)\n+    # OK, but has an alternate reference.  The alternate reference name\n+    # and other status can be given in options\n+    H: PKT-LINE(ok <ref>)\n+    H: PKT-LINE(option refname <refname>)\n+    H: PKT-LINE(option old-oid <old-oid>)\n+    H: PKT-LINE(option new-oid <new-oid>)\n+    H: PKT-LINE(option forced-update)\n+    H: ... ...\n+    H: flush-pkt\n+\n+Each command for the 'proc-receive' hook may point to a pseudo-reference\n+and always has a zero-old as its old-oid, while the 'proc-receive' hook\n+may update an alternate reference and the alternate reference may exist\n+already with a non-zero old-oid.  For this case, this hook will use\n+\"option\" directives to report extended attributes for the reference given\n+by the above \"ok/ng\" directive.\n+\n+The report of the commands of this hook should have the same order as\n+the input.  The exit status of the 'proc-receive' hook only determines\n+the success or failure of the group of commands sent to it, unless\n+atomic push is in use.\n+\n [[post-receive]]\n post-receive\n ~~~~~~~~~~~~\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"397327","messageId":"20200507172713.GA3645853@coredump.intra.peff.net","threadId":"52936","inReplyTo":"CANYiYbFCG1_hdy1kA06wjrvGSQ2=y-hJX3Rsh-rn=X9tcqv1VQ@mail.gmail.com","subject":"Re: [PATCH v12 3/7] receive-pack: add new proc-receive hook","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-05-07T17:27:13Z","receivedAt":"2020-05-07T17:27:16Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Apr 30, 2020 at 11:33:27PM +0800, Jiang Xin wrote:\n\n> Thanks to Peff for providing technical details of the architecture.  I\n> understand that \"receive-pack\" of GitHub backend is not involved in\n> references update (executing the commands), so the \"proc-receive\" hook\n> won't be turned on for GitHub's architecture. While in our\n> architecture (inspired by \"spokes\" of GitHub), the proxy will deliver\n> not only packfile, but also commands to all three replicas. The proxy\n> will execute \"receive-pack\" on the replica with a special argument, so\n> the proxy can talk with \"receive-pack\" with an extended protocol.\n> After running pre-receive hook and release the packfile from\n> quarantine, the replica will stop and wait for the proxy to\n> coordinate. After creating a distributed lock, the proxy will tell all\n> the replicas continue to update the references.  One problem we met is\n> the proc-receive and the post-receive hook must be executed once. We\n> can make the execution of the hooks idempotent, or let only one of the\n> replica run the hook. We choose the latter.\n\nOK, that makes more sense. We solve that by not updating the refs at all\nvia receive-pack (which gives us flexibility to run our own hooks\nseparately on just one replica, etc).\n\n> I agree that adding new capability (report-status-v2) is a better\n> solution, but I think the above extension is a bit wordy. We add\n> additional 18 characters for each \"ok <ref>\" status, and add\n> additional 32 characters for each \"ng <ref> <msg>\" status. Can we\n> extend it like this:\n> \n>     ok <ref>\n>     [optional key-value pairs]\n>     ng <ref> <error message>\n>     [optional key-value pairs]\n\nI doubt the number of bytes is all that important, but certainly I'm\nhappy with this (and it looks like what you put into v14).\n\n-Peff\n"},{"id":"398042","messageId":"20200518094039.757-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200507161057.12690-1-worldhello.net@gmail.com","subject":"[PATCH v16 01/11] transport: not report a non-head push as a branch","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-18T09:40:29Z","receivedAt":"2020-05-18T09:40:52Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing a new reference (not a head or tag), report it as a new\nreference instead of a new branch.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5516-fetch-push.sh | 2 +-\n transport.c           | 9 ++++++---\n 2 files changed, 7 insertions(+), 4 deletions(-)\n\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 9c6218f568..77938db77f 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1039,7 +1039,7 @@ test_force_fetch_tag \"annotated tag\" \"-f -a -m'tag message'\"\n test_expect_success 'push --porcelain' '\n \tmk_empty testrepo &&\n \techo >.git/foo  \"To testrepo\" &&\n-\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new branch]\"  &&\n+\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new reference]\"  &&\n \techo >>.git/foo \"Done\" &&\n \tgit push >.git/bar --porcelain  testrepo refs/heads/master:refs/remotes/origin/master &&\n \t(\ndiff --git a/transport.c b/transport.c\nindex 15f5ba4e8f..26fefd13c1 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -500,9 +500,12 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\t\t\t porcelain, summary_width);\n \telse if (is_null_oid(&ref->old_oid))\n \t\tprint_ref_status('*',\n-\t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n-\t\t\t\"[new branch]\"),\n-\t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t  ? \"[new tag]\"\n+\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t     ? \"[new branch]\"\n+\t\t\t\t     : \"[new reference]\")),\n+\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"398043","messageId":"20200518094039.757-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200507161057.12690-1-worldhello.net@gmail.com","subject":"[PATCH v16 00/11] New proc-receive hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-18T09:40:28Z","receivedAt":"2020-05-18T09:40:52Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThis topic introduces a new hook named \"proc-receive\" for\n\"receive-pack\".  We can use this hook to implement a Gerrit-like\ncentralized workflow.\n\n## Changes since v15\n\n1. Parse report options in function `transport_update_tracking_ref()`\n   in order to update remote-tracking branches properly.\n\n   See patch 9/11 and 10/11.\n\n2. Add docs for 'report-status-v2' in 'Documentation/technical/\n   pack-protocol.txt', and other files.\n\n   See patch 5/11.\n\n3. Remove unnecessary headers:\n\n    --- a/remote.c\n    +++ b/remote.c\n    @@ -1,6 +1,5 @@\n     #include \"cache.h\"\n     #include \"config.h\"\n    -#include \"connect.h\"\n     #include \"remote.h\"\n     #include \"refs.h\"\n     #include \"refspec.h\"\n    \n    --- a/t/helper/test-proc-receive.c\n    +++ b/t/helper/test-proc-receive.c\n    @@ -3,7 +3,6 @@\n     #include \"parse-options.h\"\n     #include \"pkt-line.h\"\n     #include \"sigchain.h\"\n    -#include \"string-list.h\"\n     #include \"test-tool.h\"\n     \n     static const char *proc_receive_usage[] = {\n\n\n4. Fix format issues:\n\n    --- a/builtin/receive-pack.c\n    +++ b/builtin/receive-pack.c\n    @@ -2262,7 +2262,7 @@ static void report(struct command *commands, const char *unpack_status)\n     \tfor (cmd = commands; cmd; cmd = cmd->next) {\n     \t\tif (!cmd->report.error_message)\n     \t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n    -\t\t\t\t\t\t cmd->ref_name);\n    +\t\t\t\t\t cmd->ref_name);\n     \t\telse\n     \t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n     \t\t\t\t\t cmd->ref_name, cmd->report.error_message);\n\n    --- a/builtin/send-pack.c\n    +++ b/builtin/send-pack.c\n    @@ -101,13 +101,13 @@ static void print_helper_status(struct ref *ref)\n     \t\t\t\tstrbuf_addf(&buf, \"ok %s\\n\", ref->name);\n     \t\t\tif (options->ref_name)\n     \t\t\t\tstrbuf_addf(&buf, \"option refname %s\\n\",\n    -\t\t\t\t\t\t  options->ref_name);\n    +\t\t\t\t\t    options->ref_name);\n     \t\t\tif (options->old_oid)\n     \t\t\t\tstrbuf_addf(&buf, \"option old-oid %s\\n\",\n    -\t\t\t\t\t\t  oid_to_hex(options->old_oid));\n    +\t\t\t\t\t    oid_to_hex(options->old_oid));\n     \t\t\tif (options->new_oid)\n     \t\t\t\tstrbuf_addf(&buf, \"option new-oid %s\\n\",\n    -\t\t\t\t\t\t  oid_to_hex(options->new_oid));\n    +\t\t\t\t\t    oid_to_hex(options->new_oid));\n     \t\t\tif (options->forced_update)\n     \t\t\t\tstrbuf_addstr(&buf, \"option forced-update\\n\");\n     \t\t}\n\n5. Other minor changes:\n\n    --- a/builtin/receive-pack.c\n    +++ b/builtin/receive-pack.c\n    @@ -806,7 +806,7 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n     \tif (options) {\n     \t\tstruct object_id *old_oid;\n     \t\tstruct object_id *new_oid;\n    -\t\tchar *ref_name;\n    +\t\tconst char *ref_name;\n     \n     \t\told_oid = options->old_oid ? options->old_oid : &cmd->old_oid;\n     \t\tnew_oid = options->new_oid ? options->new_oid : &cmd->new_oid;\n    \n    \n    --- a/remote.h\n    +++ b/remote.h\n    @@ -94,7 +94,7 @@ int for_each_remote(each_remote_fn fn, void *priv);\n     int remote_has_url(struct remote *remote, const char *url);\n     \n     struct ref_push_report_options {\n    -\tchar *ref_name;\n    +\tconst char *ref_name;\n     \tstruct object_id *old_oid;\n     \tstruct object_id *new_oid;\n     \tunsigned int forced_update:1;\n    \n----\n\nJiang Xin (11):\n  transport: not report a non-head push as a branch\n  t5411: add basic test cases for proc-receive hook\n  receive-pack: add new proc-receive hook\n  New capability \"report-status-v2\" for git-push\n  doc: add document for capability report-status-v2\n  receive-pack: feed report options to post-receive\n  refs.c: refactor to reuse ref_is_hidden()\n  receive-pack: new config receive.procReceiveRefs\n  t5411: test updates of remote-tracking branches\n  transport: parse report options for tracking refs\n  doc: add documentation for the proc-receive hook\n\n Documentation/config/receive.txt              |  14 +\n Documentation/githooks.txt                    |  62 +++\n Documentation/technical/pack-protocol.txt     |  43 +-\n .../technical/protocol-capabilities.txt       |  17 +-\n Makefile                                      |   1 +\n builtin/receive-pack.c                        | 461 ++++++++++++++++--\n builtin/send-pack.c                           |  21 +-\n refs.c                                        |  13 +-\n refs.h                                        |   1 +\n remote.c                                      |   4 +-\n remote.h                                      |  16 +-\n send-pack.c                                   |  99 +++-\n t/helper/test-proc-receive.c                  | 174 +++++++\n t/helper/test-tool.c                          |   1 +\n t/helper/test-tool.h                          |   1 +\n t/t5411-proc-receive-hook.sh                  | 117 +++++\n t/t5411/common-functions.sh                   |  56 +++\n t/t5411/once-0010-report-status-v1.sh         |  88 ++++\n t/t5411/test-0000-standard-git-push.sh        | 143 ++++++\n .../test-0001-standard-git-push--porcelain.sh | 147 ++++++\n t/t5411/test-0002-pre-receive-declined.sh     |  33 ++\n ...st-0003-pre-receive-declined--porcelain.sh |  34 ++\n t/t5411/test-0010-proc-receive-settings.sh    |   7 +\n t/t5411/test-0011-no-hook-error.sh            |  64 +++\n t/t5411/test-0012-no-hook-error--porcelain.sh |  66 +++\n t/t5411/test-0013-bad-protocol.sh             | 217 +++++++++\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 160 ++++++\n t/t5411/test-0020-report-ng.sh                |  67 +++\n t/t5411/test-0021-report-ng--porcelain.sh     |  69 +++\n t/t5411/test-0022-report-unexpect-ref.sh      |  45 ++\n ...est-0023-report-unexpect-ref--porcelain.sh |  46 ++\n t/t5411/test-0024-report-unknown-ref.sh       |  34 ++\n ...test-0025-report-unknown-ref--porcelain.sh |  35 ++\n t/t5411/test-0026-push-options.sh             |  79 +++\n t/t5411/test-0027-push-options--porcelain.sh  |  82 ++++\n t/t5411/test-0030-report-ok.sh                |  35 ++\n t/t5411/test-0031-report-ok--porcelain.sh     |  36 ++\n t/t5411/test-0032-report-with-options.sh      | 256 ++++++++++\n ...est-0033-report-with-options--porcelain.sh | 265 ++++++++++\n t/t5411/test-0034-report-ft.sh                |  44 ++\n t/t5411/test-0035-report-ft--porcelain.sh     |  45 ++\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 227 +++++++++\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 172 +++++++\n t/t5411/test-0038-report-mixed-refs.sh        |  89 ++++\n .../test-0039-report-mixed-refs--porcelain.sh |  91 ++++\n t/t5411/test-0040-process-all-refs.sh         | 113 +++++\n .../test-0041-process-all-refs--porcelain.sh  | 114 +++++\n t/t5516-fetch-push.sh                         |   2 +-\n transport-helper.c                            |  53 +-\n transport.c                                   | 161 ++++--\n 50 files changed, 4109 insertions(+), 111 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/once-0010-report-status-v1.sh\n create mode 100644 t/t5411/test-0000-standard-git-push.sh\n create mode 100644 t/t5411/test-0001-standard-git-push--porcelain.sh\n create mode 100644 t/t5411/test-0002-pre-receive-declined.sh\n create mode 100644 t/t5411/test-0003-pre-receive-declined--porcelain.sh\n create mode 100644 t/t5411/test-0010-proc-receive-settings.sh\n create mode 100644 t/t5411/test-0011-no-hook-error.sh\n create mode 100644 t/t5411/test-0012-no-hook-error--porcelain.sh\n create mode 100644 t/t5411/test-0013-bad-protocol.sh\n create mode 100644 t/t5411/test-0014-bad-protocol--porcelain.sh\n create mode 100644 t/t5411/test-0020-report-ng.sh\n create mode 100644 t/t5411/test-0021-report-ng--porcelain.sh\n create mode 100644 t/t5411/test-0022-report-unexpect-ref.sh\n create mode 100644 t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n create mode 100644 t/t5411/test-0024-report-unknown-ref.sh\n create mode 100644 t/t5411/test-0025-report-unknown-ref--porcelain.sh\n create mode 100644 t/t5411/test-0026-push-options.sh\n create mode 100644 t/t5411/test-0027-push-options--porcelain.sh\n create mode 100644 t/t5411/test-0030-report-ok.sh\n create mode 100644 t/t5411/test-0031-report-ok--porcelain.sh\n create mode 100644 t/t5411/test-0032-report-with-options.sh\n create mode 100644 t/t5411/test-0033-report-with-options--porcelain.sh\n create mode 100644 t/t5411/test-0034-report-ft.sh\n create mode 100644 t/t5411/test-0035-report-ft--porcelain.sh\n create mode 100644 t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n create mode 100644 t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n create mode 100644 t/t5411/test-0038-report-mixed-refs.sh\n create mode 100644 t/t5411/test-0039-report-mixed-refs--porcelain.sh\n create mode 100644 t/t5411/test-0040-process-all-refs.sh\n create mode 100644 t/t5411/test-0041-process-all-refs--porcelain.sh\n\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"398044","messageId":"20200518094039.757-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200507161057.12690-1-worldhello.net@gmail.com","subject":"[PATCH v16 02/11] t5411: add basic test cases for proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-18T09:40:30Z","receivedAt":"2020-05-18T09:40:55Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nTopic \"proc-receive-hook\" will change the workflow and output of\ngit-push. Add some basic test cases in t5411 before introducing the new\ntopic.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411-proc-receive-hook.sh                  | 108 +++++++++++++\n t/t5411/common-functions.sh                   |  56 +++++++\n t/t5411/test-0000-standard-git-push.sh        | 143 +++++++++++++++++\n .../test-0001-standard-git-push--porcelain.sh | 147 ++++++++++++++++++\n t/t5411/test-0002-pre-receive-declined.sh     |  33 ++++\n ...st-0003-pre-receive-declined--porcelain.sh |  34 ++++\n 6 files changed, 521 insertions(+)\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/test-0000-standard-git-push.sh\n create mode 100644 t/t5411/test-0001-standard-git-push--porcelain.sh\n create mode 100644 t/t5411/test-0002-pre-receive-declined.sh\n create mode 100644 t/t5411/test-0003-pre-receive-declined--porcelain.sh\n\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nnew file mode 100755\nindex 0000000000..3a684353a8\n--- /dev/null\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -0,0 +1,108 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook'\n+\n+. ./test-lib.sh\n+\n+. \"$TEST_DIRECTORY\"/t5411/common-functions.sh\n+\n+setup_upstream_and_workbench () {\n+\t# Refs of upstream : master(A)\n+\t# Refs of workbench: master(A)  tags/v123\n+\ttest_expect_success \"setup upstream and workbench\" '\n+\t\trm -rf upstream.git &&\n+\t\trm -rf workbench &&\n+\t\tgit init --bare upstream.git &&\n+\t\tgit init workbench &&\n+\t\tcreate_commits_in workbench A B &&\n+\t\t(\n+\t\t\tcd workbench &&\n+\t\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\t\tgit config core.abbrev 7 &&\n+\t\t\tgit tag -m \"v123\" v123 $A &&\n+\t\t\tgit remote add origin ../upstream.git &&\n+\t\t\tgit push origin master &&\n+\t\t\tgit update-ref refs/heads/master $A $B &&\n+\t\t\tgit -C ../upstream.git update-ref \\\n+\t\t\t\trefs/heads/master $A $B\n+\t\t) &&\n+\t\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t\t# setup pre-receive hook\n+\t\twrite_script upstream.git/hooks/pre-receive <<-\\EOF &&\n+\t\texec >&2\n+\t\techo \"# pre-receive hook\"\n+\t\twhile read old new ref\n+\t\tdo\n+\t\t\techo \"pre-receive< $old $new $ref\"\n+\t\tdone\n+\t\tEOF\n+\n+\t\t# setup post-receive hook\n+\t\twrite_script upstream.git/hooks/post-receive <<-\\EOF &&\n+\t\texec >&2\n+\t\techo \"# post-receive hook\"\n+\t\twhile read old new ref\n+\t\tdo\n+\t\t\techo \"post-receive< $old $new $ref\"\n+\t\tdone\n+\t\tEOF\n+\n+\t\tupstream=upstream.git\n+\t'\n+}\n+\n+run_proc_receive_hook_test() {\n+\tcase $1 in\n+\thttp)\n+\t\tPROTOCOL=\"HTTP protocol\"\n+\t\tURL_PREFIX=\"http://.*\"\n+\t\t;;\n+\tlocal)\n+\t\tPROTOCOL=\"builtin protocol\"\n+\t\tURL_PREFIX=\"\\.\\.\"\n+\t\t;;\n+\tesac\n+\n+\t# Include test cases for both file and HTTP protocol\n+\tfor t in  \"$TEST_DIRECTORY\"/t5411/test-*.sh\n+\tdo\n+\t\t. \"$t\"\n+\tdone\n+}\n+\n+# Initialize the upstream repository and local workbench.\n+setup_upstream_and_workbench\n+\n+# Run test cases for 'proc-receive' hook on local file protocol.\n+run_proc_receive_hook_test local\n+\n+ROOT_PATH=\"$PWD\"\n+. \"$TEST_DIRECTORY\"/lib-gpg.sh\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+. \"$TEST_DIRECTORY\"/lib-terminal.sh\n+start_httpd\n+\n+# Re-initialize the upstream repository and local workbench.\n+setup_upstream_and_workbench\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup for HTTP protocol\" '\n+\tgit -C upstream.git config http.receivepack true &&\n+\tupstream=\"$HTTPD_DOCUMENT_ROOT_PATH/upstream.git\" &&\n+\tmv upstream.git \"$upstream\" &&\n+\tgit -C workbench remote set-url origin \"$HTTPD_URL/auth-push/smart/upstream.git\" &&\n+\tset_askpass user@host pass@host\n+'\n+\n+setup_askpass_helper\n+\n+# Run test cases for 'proc-receive' hook on HTTP protocol.\n+run_proc_receive_hook_test http\n+\n+test_done\ndiff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\nnew file mode 100644\nindex 0000000000..6580bebd8e\n--- /dev/null\n+++ b/t/t5411/common-functions.sh\n@@ -0,0 +1,56 @@\n+# Create commits in <repo> and assign each commit's oid to shell variables\n+# given in the arguments (A, B, and C). E.g.:\n+#\n+#     create_commits_in <repo> A B C\n+#\n+# NOTE: Never calling this function from a subshell since variable\n+# assignments will disappear when subshell exits.\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{} --)\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  We can easily prepare the expect text\n+# without having to worry about future changes of the commit ID and spaces\n+# of the output.  Single quotes are replaced with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect text.  We also\n+# remove some locale error messages, which break test if we turn on\n+# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n+# on plumbing commands.\n+make_user_friendly_and_stable_output () {\n+\tsed \\\n+\t\t-e \"s/  *\\$//\" \\\n+\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/'/\\\"/g\" \\\n+\t\t-e \"s/\t/    /g\" \\\n+\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n+\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n+\t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n+\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n+\t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n+\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\" \\\n+\t\t-e \"s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#\" \\\n+\t\t-e \"/^error: / d\"\n+}\ndiff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh\nnew file mode 100644\nindex 0000000000..e206587348\n--- /dev/null\n+++ b/t/t5411/test-0000-standard-git-push.sh\n@@ -0,0 +1,143 @@\n+# Refs of upstream : master(A)  \n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t * [new branch] HEAD -> next\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push --atomic: master(A)  next(B)\n+test_expect_success \"git-push --atomic ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\tmaster \\\n+\t\t$B:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out |\n+\t\tsed -n \\\n+\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n+\t\t\t-e \"/^ ! / { s/   */ /g; p; }\" \\\n+\t\t\t>actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [rejected] master -> master (non-fast-forward)\n+\t ! [rejected] <COMMIT-B> -> next (atomic push failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push         : master(A)  next(B)\n+test_expect_success \"non-fast-forward git-push ($PROTOCOL)\" '\n+\ttest_must_fail git \\\n+\t\t-C workbench \\\n+\t\t-c advice.pushUpdateRejected=false \\\n+\t\tpush origin \\\n+\t\tmaster \\\n+\t\t$B:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> next\n+\t ! [rejected] master -> master (non-fast-forward)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-B> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(B)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"git-push -f ($PROTOCOL)\" '\n+\tgit -C workbench push -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tmaster \\\n+\t\tmaster:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-B> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-B> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo <URL/of/upstream.git>\n+\t + <OID-B>...<OID-A> master -> master (forced update)\n+\t - [deleted] next\n+\t * [new tag] v123 -> v123\n+\t * [new reference] master -> refs/review/master/topic\n+\t * [new branch] HEAD -> a/b/c\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\ndiff --git a/t/t5411/test-0001-standard-git-push--porcelain.sh b/t/t5411/test-0001-standard-git-push--porcelain.sh\nnew file mode 100644\nindex 0000000000..48f6fcc846\n--- /dev/null\n+++ b/t/t5411/test-0001-standard-git-push--porcelain.sh\n@@ -0,0 +1,147 @@\n+# Refs of upstream : master(A)  \n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push --atomic: master(A)  next(B)\n+test_expect_success \"git-push --atomic ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --atomic --porcelain origin \\\n+\t\tmaster \\\n+\t\t$B:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out |\n+\t\tsed -n \\\n+\t\t\t-e \"s/^# GETTEXT POISON #//\" \\\n+\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n+\t\t\t-e \"/^! / { s/   */ /g; p; }\" \\\n+\t\t\t>actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t! refs/heads/master:refs/heads/master [rejected] (non-fast-forward)\n+\t! <COMMIT-B>:refs/heads/next [rejected] (atomic push failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push         : master(A)  next(B)\n+test_expect_success \"non-fast-forward git-push ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git \\\n+\t\t-C workbench \\\n+\t\t-c advice.pushUpdateRejected=false \\\n+\t\tpush --porcelain origin \\\n+\t\tmaster \\\n+\t\t$B:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/next    <OID-A>..<OID-B>\n+\t!    refs/heads/master:refs/heads/master    [rejected] (non-fast-forward)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-B> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(B)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"git-push -f ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tmaster \\\n+\t\tmaster:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-B> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-B> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo <URL/of/upstream.git>\n+\t+    refs/heads/master:refs/heads/master    <OID-B>...<OID-A> (forced update)\n+\t-    :refs/heads/next    [deleted]\n+\t*    refs/tags/v123:refs/tags/v123    [new tag]\n+\t*    refs/heads/master:refs/review/master/topic    [new reference]\n+\t*    HEAD:refs/heads/a/b/c    [new branch]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\ndiff --git a/t/t5411/test-0002-pre-receive-declined.sh b/t/t5411/test-0002-pre-receive-declined.sh\nnew file mode 100644\nindex 0000000000..c246f7e68e\n--- /dev/null\n+++ b/t/t5411/test-0002-pre-receive-declined.sh\n@@ -0,0 +1,33 @@\n+test_expect_success \"setup pre-receive hook ($PROTOCOL)\" '\n+\tmv \"$upstream/hooks/pre-receive\" \"$upstream/hooks/pre-receive.ok\" &&\n+\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n+\texit 1\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push is declined ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] <COMMIT-B> -> master (pre-receive hook declined)\n+\t ! [remote rejected] HEAD -> next (pre-receive hook declined)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tmv \"$upstream/hooks/pre-receive.ok\" \"$upstream/hooks/pre-receive\"\n+'\ndiff --git a/t/t5411/test-0003-pre-receive-declined--porcelain.sh b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\nnew file mode 100644\nindex 0000000000..b14894de81\n--- /dev/null\n+++ b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\n@@ -0,0 +1,34 @@\n+test_expect_success \"setup pre-receive hook ($PROTOCOL/porcelain)\" '\n+\tmv \"$upstream/hooks/pre-receive\" \"$upstream/hooks/pre-receive.ok\" &&\n+\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n+\texit 1\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push is declined ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    <COMMIT-B>:refs/heads/master    [remote rejected] (pre-receive hook declined)\n+\t!    HEAD:refs/heads/next    [remote rejected] (pre-receive hook declined)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tmv \"$upstream/hooks/pre-receive.ok\" \"$upstream/hooks/pre-receive\"\n+'\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"398045","messageId":"20200518094039.757-5-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200507161057.12690-1-worldhello.net@gmail.com","subject":"[PATCH v16 04/11] New capability \"report-status-v2\" for git-push","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-18T09:40:32Z","receivedAt":"2020-05-18T09:40:56Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThe new introduced \"proc-receive\" hook may handle a command for a\npseudo-reference with a zero-old as its old-oid, while the hook may\ncreate or update a reference with different name, different new-oid,\nand different old-oid (the reference may exist already with a non-zero\nold-oid).  Current \"report-status\" protocol cannot report the status for\nsuch reference rewrite.\n\nAdd new capability \"report-status-v2\" and new report protocol which is\nnot backward compatible for report of git-push.\n\nIf a user pushes to a pseudo-reference \"refs/for/master/topic\", and\n\"receive-pack\" creates two new references \"refs/changes/23/123/1\" and\n\"refs/changes/24/124/1\", for client without the knowledge of\n\"report-status-v2\", \"receive-pack\" will only send \"ok/ng\" directives in\nthe report, such as:\n\n    ok ref/for/master/topic\n\nBut for client which has the knowledge of \"report-status-v2\",\n\"receive-pack\" will use \"option\" directives to report more attributes\nfor the reference given by the above \"ok/ng\" directive.\n\n    ok refs/for/master/topic\n    option refname refs/changes/23/123/1\n    option new-oid <new-oid>\n    ok refs/for/master/topic\n    option refname refs/changes/24/124/1\n    option new-oid <new-oid>\n\nThe client will report two new created references to the end user.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSuggested-by: Jeff King <peff@peff.net>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c                        |  52 +++++++-\n builtin/send-pack.c                           |  21 +++-\n remote.c                                      |   4 +-\n remote.h                                      |   2 +-\n send-pack.c                                   |  99 ++++++++++++---\n t/t5411-proc-receive-hook.sh                  |   9 ++\n t/t5411/once-0010-report-status-v1.sh         |  83 ++++++++++++\n t/t5411/test-0032-report-with-options.sh      |  14 +--\n ...est-0033-report-with-options--porcelain.sh |  14 +--\n ...t-0036-report-multi-rewrite-for-one-ref.sh |  11 +-\n ...rt-multi-rewrite-for-one-ref--porcelain.sh |  11 +-\n t/t5411/test-0038-report-mixed-refs.sh        |   2 +-\n .../test-0039-report-mixed-refs--porcelain.sh |   2 +-\n transport-helper.c                            |  53 +++++++-\n transport.c                                   | 118 +++++++++++++-----\n 15 files changed, 418 insertions(+), 77 deletions(-)\n create mode 100644 t/t5411/once-0010-report-status-v1.sh\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 717bebd3e1..5544d03718 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -57,6 +57,7 @@ static int advertise_push_options;\n static int unpack_limit = 100;\n static off_t max_input_size;\n static int report_status;\n+static int report_status_v2;\n static int use_sideband;\n static int use_atomic;\n static int use_push_options;\n@@ -240,7 +241,7 @@ static void show_ref(const char *path, const struct object_id *oid)\n \t\tstruct strbuf cap = STRBUF_INIT;\n \n \t\tstrbuf_addstr(&cap,\n-\t\t\t      \"report-status delete-refs side-band-64k quiet\");\n+\t\t\t      \"report-status report-status-v2 delete-refs side-band-64k quiet\");\n \t\tif (advertise_atomic_push)\n \t\t\tstrbuf_addstr(&cap, \" atomic\");\n \t\tif (prefer_ofs_delta)\n@@ -1915,6 +1916,8 @@ static struct command *read_head_info(struct packet_reader *reader,\n \t\t\tconst char *feature_list = reader->line + linelen + 1;\n \t\t\tif (parse_feature_request(feature_list, \"report-status\"))\n \t\t\t\treport_status = 1;\n+\t\t\tif (parse_feature_request(feature_list, \"report-status-v2\"))\n+\t\t\t\treport_status_v2 = 1;\n \t\t\tif (parse_feature_request(feature_list, \"side-band-64k\"))\n \t\t\t\tuse_sideband = LARGE_PACKET_MAX;\n \t\t\tif (parse_feature_request(feature_list, \"quiet\"))\n@@ -2226,6 +2229,49 @@ static void report(struct command *commands, const char *unpack_status)\n \tstrbuf_release(&buf);\n }\n \n+static void report_v2(struct command *commands, const char *unpack_status)\n+{\n+\tstruct command *cmd;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ref_push_report_options *options;\n+\n+\tpacket_buf_write(&buf, \"unpack %s\\n\",\n+\t\t\t unpack_status ? unpack_status : \"ok\");\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tint count = 0;\n+\n+\t\tif (!cmd->report.error_message)\n+\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n+\t\t\t\t\t cmd->ref_name);\n+\t\telse\n+\t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n+\t\t\t\t\t cmd->ref_name,\n+\t\t\t\t\t cmd->report.error_message);\n+\t\tfor (options = cmd->report.options; options; options = options->next) {\n+\t\t\tif (count++ > 0)\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s\\n\", cmd->ref_name);\n+\t\t\tif (options->ref_name)\n+\t\t\t\tpacket_buf_write(&buf, \"option refname %s\\n\",\n+\t\t\t\t\t\t options->ref_name);\n+\t\t\tif (options->old_oid)\n+\t\t\t\tpacket_buf_write(&buf, \"option old-oid %s\\n\",\n+\t\t\t\t\t\t oid_to_hex(options->old_oid));\n+\t\t\tif (options->new_oid)\n+\t\t\t\tpacket_buf_write(&buf, \"option new-oid %s\\n\",\n+\t\t\t\t\t\t oid_to_hex(options->new_oid));\n+\t\t\tif (options->forced_update)\n+\t\t\t\tpacket_buf_write(&buf, \"option forced-update\\n\");\n+\t\t}\n+\t}\n+\tpacket_buf_flush(&buf);\n+\n+\tif (use_sideband)\n+\t\tsend_sideband(1, 1, buf.buf, buf.len, use_sideband);\n+\telse\n+\t\twrite_or_die(1, buf.buf, buf.len);\n+\tstrbuf_release(&buf);\n+}\n+\n static int delete_only(struct command *commands)\n {\n \tstruct command *cmd;\n@@ -2334,7 +2380,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\t\t\t &push_options);\n \t\tif (pack_lockfile)\n \t\t\tunlink_or_warn(pack_lockfile);\n-\t\tif (report_status)\n+\t\tif (report_status_v2)\n+\t\t\treport_v2(commands, unpack_status);\n+\t\telse if (report_status)\n \t\t\treport(commands, unpack_status);\n \t\trun_receive_hook(commands, \"post-receive\", 1,\n \t\t\t\t &push_options);\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 2b9610f121..9d0df751d6 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -29,10 +29,12 @@ static struct send_pack_args args;\n static void print_helper_status(struct ref *ref)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ref_push_report_options *options;\n \n \tfor (; ref; ref = ref->next) {\n \t\tconst char *msg = NULL;\n \t\tconst char *res;\n+\t\tint count = 0;\n \n \t\tswitch(ref->status) {\n \t\tcase REF_STATUS_NONE:\n@@ -86,14 +88,29 @@ static void print_helper_status(struct ref *ref)\n \n \t\tstrbuf_reset(&buf);\n \t\tstrbuf_addf(&buf, \"%s %s\", res, ref->name);\n-\t\tif (ref->remote_status)\n-\t\t\tmsg = ref->remote_status;\n+\t\tif (ref->report.error_message)\n+\t\t\tmsg = ref->report.error_message;\n \t\tif (msg) {\n \t\t\tstrbuf_addch(&buf, ' ');\n \t\t\tquote_two_c_style(&buf, \"\", msg, 0);\n \t\t}\n \t\tstrbuf_addch(&buf, '\\n');\n \n+\t\tfor (options = ref->report.options; options; options = options->next) {\n+\t\t\tif (count++ > 0)\n+\t\t\t\tstrbuf_addf(&buf, \"ok %s\\n\", ref->name);\n+\t\t\tif (options->ref_name)\n+\t\t\t\tstrbuf_addf(&buf, \"option refname %s\\n\",\n+\t\t\t\t\t    options->ref_name);\n+\t\t\tif (options->old_oid)\n+\t\t\t\tstrbuf_addf(&buf, \"option old-oid %s\\n\",\n+\t\t\t\t\t    oid_to_hex(options->old_oid));\n+\t\t\tif (options->new_oid)\n+\t\t\t\tstrbuf_addf(&buf, \"option new-oid %s\\n\",\n+\t\t\t\t\t    oid_to_hex(options->new_oid));\n+\t\t\tif (options->forced_update)\n+\t\t\t\tstrbuf_addstr(&buf, \"option forced-update\\n\");\n+\t\t}\n \t\twrite_or_die(1, buf.buf, buf.len);\n \t}\n \tstrbuf_release(&buf);\ndiff --git a/remote.c b/remote.c\nindex 534c6426f1..cc4d542fd3 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -789,7 +789,7 @@ struct ref *copy_ref(const struct ref *ref)\n \tmemcpy(cpy, ref, len);\n \tcpy->next = NULL;\n \tcpy->symref = xstrdup_or_null(ref->symref);\n-\tcpy->remote_status = xstrdup_or_null(ref->remote_status);\n+\tcpy->report.error_message = xstrdup_or_null(ref->report.error_message);\n \tcpy->peer_ref = copy_ref(ref->peer_ref);\n \treturn cpy;\n }\n@@ -811,7 +811,7 @@ void free_one_ref(struct ref *ref)\n \tif (!ref)\n \t\treturn;\n \tfree_one_ref(ref->peer_ref);\n-\tfree(ref->remote_status);\n+\tfree((void *)ref->report.error_message);\n \tfree(ref->symref);\n \tfree(ref);\n }\ndiff --git a/remote.h b/remote.h\nindex 6dbc7e0559..4e9a17de3c 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -153,7 +153,7 @@ struct ref {\n \t\tREF_STATUS_EXPECTING_REPORT,\n \t\tREF_STATUS_ATOMIC_PUSH_FAILED\n \t} status;\n-\tchar *remote_status;\n+\tstruct ref_push_report report;\n \tstruct ref *peer_ref; /* when renaming */\n \tchar name[FLEX_ARRAY]; /* more */\n };\ndiff --git a/send-pack.c b/send-pack.c\nindex 0abee22283..d8a6db47c7 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -154,25 +154,79 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n {\n \tstruct ref *hint;\n \tint ret;\n+\tint new_options = 1;\n \n \thint = NULL;\n \tret = receive_unpack_status(reader);\n \twhile (1) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *head;\n \t\tconst char *refname;\n-\t\tchar *msg;\n+\t\tchar *p;\n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n \t\t\tbreak;\n-\t\tif (!starts_with(reader->line, \"ok \") && !starts_with(reader->line, \"ng \")) {\n-\t\t\terror(\"invalid ref status from remote: %s\", reader->line);\n+\t\thead = reader->line;\n+\t\tp = strchr(head, ' ');\n+\t\tif (!p) {\n+\t\t\terror(\"invalid status line from remote: %s\", reader->line);\n \t\t\tret = -1;\n \t\t\tbreak;\n \t\t}\n+\t\t*p++ = '\\0';\n+\n+\t\tif (!strcmp(head, \"option\")) {\n+\t\t\tstruct ref_push_report_options *options;\n+\t\t\tconst char *key, *val;\n+\n+\t\t\tif (!hint) {\n+\t\t\t       if (new_options) {\n+\t\t\t\t\terror(\"'option' without a matching 'ok/ng' directive\");\n+\t\t\t\t\tnew_options = 0;\n+\t\t\t\t}\n+\t\t\t\tret = -1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\toptions = hint->report.options;\n+\t\t\twhile (options && options->next)\n+\t\t\t\toptions = options->next;\n+\t\t\tif (new_options) {\n+\t\t\t\tif (!options) {\n+\t\t\t\t\thint->report.options = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\t\toptions = hint->report.options;\n+\t\t\t\t} else {\n+\t\t\t\t\toptions->next = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\t\toptions = options->next;\n+\t\t\t\t}\n+\t\t\t\tnew_options = 0;\n+\t\t\t}\n+\t\t\tassert(options);\n+\t\t\tkey = p;\n+\t\t\tp = strchr(key, ' ');\n+\t\t\tif (p)\n+\t\t\t\t*p++ = '\\0';\n+\t\t\tval = p;\n+\t\t\tif (!strcmp(key, \"refname\"))\n+\t\t\t\toptions->ref_name = xstrdup_or_null(val);\n+\t\t\telse if (!strcmp(key, \"old-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &old_oid, &val))\n+\t\t\t\toptions->old_oid = oiddup(&old_oid);\n+\t\t\telse if (!strcmp(key, \"new-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &new_oid, &val))\n+\t\t\t\toptions->new_oid = oiddup(&new_oid);\n+\t\t\telse if (!strcmp(key, \"forced-update\"))\n+\t\t\t\toptions->forced_update = 1;\n+\t\t\tcontinue;\n+\t\t}\n \n-\t\trefname = reader->line + 3;\n-\t\tmsg = strchr(refname, ' ');\n-\t\tif (msg)\n-\t\t\t*msg++ = '\\0';\n-\n+\t\tif (strcmp(head, \"ok\") && strcmp(head, \"ng\")) {\n+\t\t\terror(\"invalid ref status from remote: %s\", head);\n+\t\t\tret = -1;\n+\t\t\tbreak;\n+\t\t}\n+\t\trefname = p;\n+\t\tp = strchr(refname, ' ');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n \t\t/* first try searching at our hint, falling back to all refs */\n \t\tif (hint)\n \t\t\thint = find_ref_by_name(hint, refname);\n@@ -183,19 +237,24 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \t\t\t\t\trefname);\n \t\t\tcontinue;\n \t\t}\n-\t\tif (hint->status != REF_STATUS_EXPECTING_REPORT) {\n+\t\tif (hint->status != REF_STATUS_EXPECTING_REPORT &&\n+\t\t    hint->status != REF_STATUS_OK &&\n+\t\t    hint->status != REF_STATUS_REMOTE_REJECT) {\n \t\t\twarning(\"remote reported status on unexpected ref: %s\",\n \t\t\t\t\trefname);\n \t\t\tcontinue;\n \t\t}\n-\n-\t\tif (reader->line[0] == 'o' && reader->line[1] == 'k')\n-\t\t\thint->status = REF_STATUS_OK;\n-\t\telse\n+\t\tif (!strcmp(head, \"ng\")) {\n \t\t\thint->status = REF_STATUS_REMOTE_REJECT;\n-\t\thint->remote_status = xstrdup_or_null(msg);\n-\t\t/* start our next search from the next ref */\n-\t\thint = hint->next;\n+\t\t\tif (p)\n+\t\t\t\thint->report.error_message = xstrdup(p);\n+\t\t\telse\n+\t\t\t\thint->report.error_message = \"failed\";\n+\t\t} else {\n+\t\t\thint->status = REF_STATUS_OK;\n+\t\t\thint->report.message = xstrdup_or_null(p);\n+\t\t}\n+\t\tnew_options = 1;\n \t}\n \treturn ret;\n }\n@@ -370,7 +429,9 @@ int send_pack(struct send_pack_args *args,\n \tstruct packet_reader reader;\n \n \t/* Does the other end support the reporting? */\n-\tif (server_supports(\"report-status\"))\n+\tif (server_supports(\"report-status-v2\"))\n+\t\tstatus_report = 2;\n+\telse if (server_supports(\"report-status\"))\n \t\tstatus_report = 1;\n \tif (server_supports(\"delete-refs\"))\n \t\tallow_deleting_refs = 1;\n@@ -419,8 +480,10 @@ int send_pack(struct send_pack_args *args,\n \n \tuse_push_options = push_options_supported && args->push_options;\n \n-\tif (status_report)\n+\tif (status_report == 1)\n \t\tstrbuf_addstr(&cap_buf, \" report-status\");\n+\telse if (status_report == 2)\n+\t\tstrbuf_addstr(&cap_buf, \" report-status-v2\");\n \tif (use_sideband)\n \t\tstrbuf_addstr(&cap_buf, \" side-band-64k\");\n \tif (quiet_supported && (args->quiet || !args->progress))\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex 3a684353a8..746487286f 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -78,6 +78,15 @@ run_proc_receive_hook_test() {\n # Initialize the upstream repository and local workbench.\n setup_upstream_and_workbench\n \n+# Load test cases that only need to be executed once.\n+for t in  \"$TEST_DIRECTORY\"/t5411/once-*.sh\n+do\n+\t. \"$t\"\n+done\n+\n+# Initialize the upstream repository and local workbench.\n+setup_upstream_and_workbench\n+\n # Run test cases for 'proc-receive' hook on local file protocol.\n run_proc_receive_hook_test local\n \ndiff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh\nnew file mode 100644\nindex 0000000000..f93d760cfd\n--- /dev/null\n+++ b/t/t5411/once-0010-report-status-v1.sh\n@@ -0,0 +1,83 @@\n+test_expect_success \"setup proc-receive hook\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic1\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/for/master/topic2\" \\\n+\t\t-r \"option refname refs/for/changes/23/123/1\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic2\" \\\n+\t\t-r \"option refname refs/for/changes/24/124/2\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\" \\\n+\t\t-r \"ng refs/for/next/topic target branch not exist\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic1(A)  foo(A)  refs/for/next/topic(A)  refs/for/master/topic2(A)\n+test_expect_success \"proc-receive: report status v1\" '\n+\t{\n+\t\tprintf \"%s %s refs/heads/master\\0report-status\\n\" \\\n+\t\t\t$A $B | packetize &&\n+\t\tprintf \"%s %s refs/for/master/topic1\\n\" \\\n+\t\t\t$ZERO_OID $A | packetize &&\n+\t\tprintf \"%s %s refs/heads/foo\\n\" \\\n+\t\t\t$ZERO_OID $A | packetize &&\n+\t\tprintf \"%s %s refs/for/next/topic\\n\" \\\n+\t\t\t$ZERO_OID $A | packetize &&\n+\t\tprintf \"%s %s refs/for/master/topic2\\n\" \\\n+\t\t\t$ZERO_OID $A | packetize &&\n+\t\tprintf 0000 &&\n+\t\tprintf \"\" | git -C \"$upstream\" pack-objects --stdout\n+\t} | git receive-pack \"$upstream\" --stateless-rpc \\\n+\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t# pre-receive hook\n+\tpre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tpre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n+\tpre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tpre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tpre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2\n+\t# proc-receive hook\n+\tproc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n+\tproc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tproc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2\n+\tproc-receive> ok refs/for/master/topic1\n+\tproc-receive> option fall-through\n+\tproc-receive> ok refs/for/master/topic2\n+\tproc-receive> option refname refs/for/changes/23/123/1\n+\tproc-receive> option new-oid <COMMIT-A>\n+\tproc-receive> ok refs/for/master/topic2\n+\tproc-receive> option refname refs/for/changes/24/124/2\n+\tproc-receive> option old-oid <COMMIT-B>\n+\tproc-receive> option new-oid <COMMIT-A>\n+\tproc-receive> option forced-update\n+\tproc-receive> ng refs/for/next/topic target branch not exist\n+\t000eunpack ok\n+\t0019ok refs/heads/master\n+\t001eok refs/for/master/topic1\n+\t0016ok refs/heads/foo\n+\t0033ng refs/for/next/topic target branch not exist\n+\t001eok refs/for/master/topic2\n+\t0000# post-receive hook\n+\tpost-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n+\tpost-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/for/master/topic1\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nindex a743aa8018..3742ed3c3d 100644\n--- a/t/t5411/test-0032-report-with-options.sh\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -56,7 +56,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL)\" '\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -89,7 +89,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -123,7 +123,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL)\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-B>..<OID-A> HEAD -> refs/pull/123/head\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -155,7 +155,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL)\" '\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-B>..<OID-A> HEAD -> refs/for/master/topic\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -189,7 +189,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL)\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -241,9 +241,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \t * [new reference] HEAD -> refs/for/a/b/c/topic\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n \ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nindex 439b97b06e..f18ba9f06f 100644\n--- a/t/t5411/test-0033-report-with-options--porcelain.sh\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -57,7 +57,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL/porcelain)\"\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -92,7 +92,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -127,7 +127,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL/\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/pull/123/head    <OID-B>..<OID-A>\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -160,7 +160,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL/porcelain)\"\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-B>..<OID-A>\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -195,7 +195,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL/\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -249,9 +249,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porc\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/next/topic    [new reference]\n+\t*    HEAD:refs/pull/123/head    [new reference]\n \t*    HEAD:refs/for/a/b/c/topic    [new reference]\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t+    HEAD:refs/pull/124/head    <OID-B>...<OID-A> (forced update)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex 12acf9ea1f..64662afdc6 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -43,7 +43,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/24/124/1\n+\t <OID-A>..<OID-B> HEAD -> refs/changes/25/125/1\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -101,7 +103,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/24/124/1\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n+\t + <OID-B>...<OID-A> HEAD -> refs/changes/25/125/1 (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -147,7 +151,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/23/123/1\n+\t <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\ndiff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\nindex f4a2c56e14..a7e29af0c2 100644\n--- a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n@@ -43,7 +43,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n+\t*    HEAD:refs/changes/24/124/1    [new reference]\n+\t     HEAD:refs/changes/25/125/1    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n@@ -102,7 +104,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/changes/24/124/1    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n+\t+    HEAD:refs/changes/25/125/1    <OID-B>...<OID-A> (forced update)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n@@ -149,7 +153,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porc\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/changes/23/123/1    [new reference]\n+\t     HEAD:refs/changes/24/124/2    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nindex 1dd6a97a97..54bec4c026 100644\n--- a/t/t5411/test-0038-report-mixed-refs.sh\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -60,7 +60,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n \t * [new branch] HEAD -> baz\n \t * [new reference] HEAD -> refs/for/next/topic2\n \t * [new branch] HEAD -> foo\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \t ! [remote rejected] HEAD -> refs/for/next/topic1 (fail to call Web API)\n \t ! [remote rejected] HEAD -> refs/for/next/topic3 (proc-receive failed to report status)\n \tEOF\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nindex 32ebf63dcf..605b3cb5c9 100644\n--- a/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -60,7 +60,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcel\n \t*    HEAD:refs/heads/baz    [new branch]\n \t*    HEAD:refs/for/next/topic2    [new reference]\n \t*    HEAD:refs/heads/foo    [new branch]\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \t!    HEAD:refs/for/next/topic1    [remote rejected] (fail to call Web API)\n \t!    HEAD:refs/for/next/topic3    [remote rejected] (proc-receive failed to report status)\n \tDone\ndiff --git a/transport-helper.c b/transport-helper.c\nindex a46afcb69d..21d6e47966 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -725,6 +725,49 @@ static int push_update_ref_status(struct strbuf *buf,\n {\n \tchar *refname, *msg;\n \tint status, forced = 0;\n+\tstatic struct ref *hint = NULL;\n+\tstatic int new_options = 1;\n+\n+\tif (starts_with(buf->buf, \"option \")) {\n+\t\tstruct ref_push_report_options *options;\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *key, *val;\n+\t\tchar *p;\n+\n+\t\tif (!hint)\n+\t\t\tdie(_(\"'option' without a matching 'ok/error' directive\"));\n+\t\toptions = hint->report.options;\n+\t\twhile (options && options->next)\n+\t\t\toptions = options->next;\n+\t\tif (new_options) {\n+\t\t\tif (!options) {\n+\t\t\t\thint->report.options = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\toptions = hint->report.options;\n+\t\t\t} else {\n+\t\t\t\toptions->next = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\toptions = options->next;\n+\t\t\t}\n+\t\t\tnew_options = 0;\n+\t\t}\n+\t\tassert(options);\n+\t\tkey = buf->buf + 7;\n+\t\tp = strchr(key, ' ');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n+\t\tval = p;\n+\t\tif (!strcmp(key, \"refname\"))\n+\t\t\toptions->ref_name = xstrdup_or_null(val);\n+\t\telse if (!strcmp(key, \"old-oid\") && val &&\n+\t\t\t !parse_oid_hex(val, &old_oid, &val))\n+\t\t\toptions->old_oid = oiddup(&old_oid);\n+\t\telse if (!strcmp(key, \"new-oid\") && val &&\n+\t\t\t !parse_oid_hex(val, &new_oid, &val))\n+\t\t\toptions->new_oid = oiddup(&new_oid);\n+\t\telse if (!strcmp(key, \"forced-update\"))\n+\t\t\toptions->forced_update = 1;\n+\t\t/* Not update remote namespace again. */\n+\t\treturn 1;\n+\t}\n \n \tif (starts_with(buf->buf, \"ok \")) {\n \t\tstatus = REF_STATUS_OK;\n@@ -787,8 +830,11 @@ static int push_update_ref_status(struct strbuf *buf,\n \t\t*ref = find_ref_by_name(remote_refs, refname);\n \tif (!*ref) {\n \t\twarning(_(\"helper reported unexpected status of %s\"), refname);\n+\t\thint = NULL;\n \t\treturn 1;\n \t}\n+\thint = *ref;\n+\tnew_options = 1;\n \n \tif ((*ref)->status != REF_STATUS_NONE) {\n \t\t/*\n@@ -801,7 +847,12 @@ static int push_update_ref_status(struct strbuf *buf,\n \n \t(*ref)->status = status;\n \t(*ref)->forced_update |= forced;\n-\t(*ref)->remote_status = msg;\n+\tif (msg) {\n+\t\tif (status == REF_STATUS_OK)\n+\t\t\t(*ref)->report.message = msg;\n+\t\telse\n+\t\t\t(*ref)->report.error_message = msg;\n+\t}\n \treturn !(status == REF_STATUS_OK);\n }\n \ndiff --git a/transport.c b/transport.c\nindex 26fefd13c1..5baa30dd74 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -461,13 +461,21 @@ void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int v\n \n static void print_ref_status(char flag, const char *summary,\n \t\t\t     struct ref *to, struct ref *from, const char *msg,\n+\t\t\t     struct ref_push_report_options *options,\n \t\t\t     int porcelain, int summary_width)\n {\n+\tconst char *to_name;\n+\n+\tif (options && options->ref_name)\n+\t\tto_name = options->ref_name;\n+\telse\n+\t\tto_name = to->name;\n+\n \tif (porcelain) {\n \t\tif (from)\n-\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to_name);\n \t\telse\n-\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to_name);\n \t\tif (msg)\n \t\t\tfprintf(stdout, \"%s (%s)\\n\", summary, msg);\n \t\telse\n@@ -481,9 +489,11 @@ static void print_ref_status(char flag, const char *summary,\n \t\tfprintf(stderr, \" %s%c %-*s%s \", red, flag, summary_width,\n \t\t\tsummary, reset);\n \t\tif (from)\n-\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(to->name));\n+\t\t\tfprintf(stderr, \"%s -> %s\",\n+\t\t\t\tprettify_refname(from->name),\n+\t\t\t\tprettify_refname(to_name));\n \t\telse\n-\t\t\tfputs(prettify_refname(to->name), stderr);\n+\t\t\tfputs(prettify_refname(to_name), stderr);\n \t\tif (msg) {\n \t\t\tfputs(\" (\", stderr);\n \t\t\tfputs(msg, stderr);\n@@ -493,27 +503,52 @@ static void print_ref_status(char flag, const char *summary,\n \t}\n }\n \n-static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_width)\n+static void print_ok_ref_status(struct ref *ref,\n+\t\t\t\tstruct ref_push_report_options *options,\n+\t\t\t\tint porcelain, int summary_width)\n {\n+\tstruct object_id *old_oid;\n+\tstruct object_id *new_oid;\n+\tconst char *ref_name;\n+\tint forced_update;\n+\n+\tif (options && options->old_oid)\n+\t\told_oid = options->old_oid;\n+\telse\n+\t\told_oid = &ref->old_oid;\n+\tif (options && options->new_oid)\n+\t\tnew_oid = options->new_oid;\n+\telse\n+\t\tnew_oid = &ref->new_oid;\n+\tif (options && options->forced_update)\n+\t\tforced_update = options->forced_update;\n+\telse\n+\t\tforced_update = ref->forced_update;\n+\tif (options && options->ref_name)\n+\t\tref_name = options->ref_name;\n+\telse\n+\t\tref_name = ref->name;\n+\n \tif (ref->deletion)\n \t\tprint_ref_status('-', \"[deleted]\", ref, NULL, NULL,\n-\t\t\t\t porcelain, summary_width);\n-\telse if (is_null_oid(&ref->old_oid))\n+\t\t\t\t options, porcelain, summary_width);\n+\telse if (is_null_oid(old_oid))\n \t\tprint_ref_status('*',\n-\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t (starts_with(ref_name, \"refs/tags/\")\n \t\t\t\t  ? \"[new tag]\"\n-\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t  : (starts_with(ref_name, \"refs/heads/\")\n \t\t\t\t     ? \"[new branch]\"\n \t\t\t\t     : \"[new reference]\")),\n-\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t ref, ref->peer_ref, NULL,\n+\t\t\t\t options, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n \t\tconst char *msg;\n \n-\t\tstrbuf_add_unique_abbrev(&quickref, &ref->old_oid,\n+\t\tstrbuf_add_unique_abbrev(&quickref, old_oid,\n \t\t\t\t\t DEFAULT_ABBREV);\n-\t\tif (ref->forced_update) {\n+\t\tif (forced_update) {\n \t\t\tstrbuf_addstr(&quickref, \"...\");\n \t\t\ttype = '+';\n \t\t\tmsg = \"forced update\";\n@@ -522,17 +557,18 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\t\ttype = ' ';\n \t\t\tmsg = NULL;\n \t\t}\n-\t\tstrbuf_add_unique_abbrev(&quickref, &ref->new_oid,\n+\t\tstrbuf_add_unique_abbrev(&quickref, new_oid,\n \t\t\t\t\t DEFAULT_ABBREV);\n \n \t\tprint_ref_status(type, quickref.buf, ref, ref->peer_ref, msg,\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t options, porcelain, summary_width);\n \t\tstrbuf_release(&quickref);\n \t}\n }\n \n-static int print_one_push_status(struct ref *ref, const char *dest, int count,\n-\t\t\t\t int porcelain, int summary_width)\n+static int _print_one_push_status(struct ref *ref, const char *dest, int count,\n+\t\t\t\t  struct ref_push_report_options *options,\n+\t\t\t\t  int porcelain, int summary_width)\n {\n \tif (!count) {\n \t\tchar *url = transport_anonymize_url(dest);\n@@ -543,65 +579,89 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count,\n \tswitch(ref->status) {\n \tcase REF_STATUS_NONE:\n \t\tprint_ref_status('X', \"[no match]\", ref, NULL, NULL,\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NODELETE:\n \t\tprint_ref_status('!', \"[rejected]\", ref, NULL,\n \t\t\t\t \"remote does not support deleting refs\",\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_UPTODATE:\n \t\tprint_ref_status('=', \"[up to date]\", ref,\n-\t\t\t\t ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t ref->peer_ref, NULL,\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NONFASTFORWARD:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"non-fast-forward\", porcelain, summary_width);\n+\t\t\t\t \"non-fast-forward\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"already exists\", porcelain, summary_width);\n+\t\t\t\t \"already exists\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_FETCH_FIRST:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"fetch first\", porcelain, summary_width);\n+\t\t\t\t \"fetch first\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NEEDS_FORCE:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"needs force\", porcelain, summary_width);\n+\t\t\t\t \"needs force\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_STALE:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"stale info\", porcelain, summary_width);\n+\t\t\t\t \"stale info\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_SHALLOW:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n \t\t\t\t \"new shallow roots not allowed\",\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REMOTE_REJECT:\n \t\tprint_ref_status('!', \"[remote rejected]\", ref,\n \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n-\t\t\t\t ref->remote_status, porcelain, summary_width);\n+\t\t\t\t ref->report.error_message,\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_EXPECTING_REPORT:\n \t\tprint_ref_status('!', \"[remote failure]\", ref,\n \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n \t\t\t\t \"remote failed to report status\",\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_ATOMIC_PUSH_FAILED:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"atomic push failed\", porcelain, summary_width);\n+\t\t\t\t \"atomic push failed\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_OK:\n-\t\tprint_ok_ref_status(ref, porcelain, summary_width);\n+\t\tprint_ok_ref_status(ref, options, porcelain, summary_width);\n \t\tbreak;\n \t}\n \n \treturn 1;\n }\n \n+static int print_one_push_status(struct ref *ref, const char *dest, int count,\n+\t\t\t\t int porcelain, int summary_width)\n+{\n+\tstruct ref_push_report_options *options;\n+\tint n = 0;\n+\n+\tif (!ref->report.options)\n+\t\treturn _print_one_push_status(ref, dest, count,\n+\t\t\t\t\t      NULL, porcelain, summary_width);\n+\n+\tfor (options = ref->report.options; options; options = options->next)\n+\t\t_print_one_push_status(ref, dest, count + n++,\n+\t\t\t\t       options, porcelain, summary_width);\n+\treturn n;\n+}\n+\n static int measure_abbrev(const struct object_id *oid, int sofar)\n {\n \tchar hex[GIT_MAX_HEXSZ + 1];\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"398050","messageId":"20200518094039.757-4-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200507161057.12690-1-worldhello.net@gmail.com","subject":"[PATCH v16 03/11] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-18T09:40:31Z","receivedAt":"2020-05-18T09:40:59Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nGit calls an internal `execute_commands` function to handle commands\nsent from client to `git-receive-pack`.  Regardless of what references\nthe user pushes, git creates or updates the corresponding references if\nthe user has write-permission.  A contributor who has no\nwrite-permission, cannot push to the repository directly.  So, the\ncontributor has to write commits to an alternate location, and sends\npull request by emails or by other ways.  We call this workflow as a\ndistributed workflow.\n\nIt would be more convenient to work in a centralized workflow like what\nGerrit provided for some cases.  For example, a read-only user who\ncannot push to a branch directly can run the following `git push`\ncommand to push commits to a pseudo reference (has a prefix \"refs/for/\",\nnot \"refs/heads/\") to create a code review.\n\n    git push origin \\\n        HEAD:refs/for/<branch-name>/<session>\n\nThe `<branch-name>` in the above example can be as simple as \"master\",\nor a more complicated branch name like \"foo/bar\".  The `<session>` in\nthe above example command can be the local branch name of the client\nside, such as \"my/topic\".\n\nWe cannot implement a centralized workflow elegantly by using\n\"pre-receive\" + \"post-receive\", because Git will call the internal\nfunction \"execute_commands\" to create references (even the special\npseudo reference) between these two hooks.  Even though we can delete\nthe temporarily created pseudo reference via the \"post-receive\" hook,\nhaving a temporary reference is not safe for concurrent pushes.\n\nSo, add a filter and a new handler to support this kind of workflow.\nThe filter will check the prefix of the reference name, and if the\ncommand has a special reference name, the filter will turn a specific\nfield (`run_proc_receive`) on for the command.  Commands with this filed\nturned on will be executed by a new handler (an hook named\n\"proc-receive\") instead of the internal `execute_commands` function.\nWe can use this \"proc-receive\" command to create pull requests or send\nemails for code review.\n\nSuggested by Junio, this \"proc-receive\" hook reads the commands,\npush-options (optional), and send result using a protocol in pkt-line\nformat.  In the following example, The letter \"S\" stands for\n\"receive-pack\" and letter \"H\" stands for the hook.\n\n    # Version and features negotiation.\n    S: PKT-LINE(version=1\\0push-options atomic...)\n    S: flush-pkt\n    H: PKT-LINE(version=1\\0push-options...)\n    H: flush-pkt\n\n    # Send commands from server to the hook.\n    S: PKT-LINE(<old-oid> <new-oid> <ref>)\n    S: ... ...\n    S: flush-pkt\n    # Send push-options only if the 'push-options' feature is enabled.\n    S: PKT-LINE(push-option)\n    S: ... ...\n    S: flush-pkt\n\n    # Receive result from the hook.\n    # OK, run this command successfully.\n    H: PKT-LINE(ok <ref>)\n    # NO, I reject it.\n    H: PKT-LINE(ng <ref> <reason>)\n    # Fall through, let 'receive-pack' to execute it.\n    H: PKT-LINE(ok <ref>)\n    H: PKT-LINE(option fall-through)\n    # OK, but has an alternate reference.  The alternate reference name\n    # and other status can be given in options\n    H: PKT-LINE(ok <ref>)\n    H: PKT-LINE(option refname <refname>)\n    H: PKT-LINE(option old-oid <old-oid>)\n    H: PKT-LINE(option new-oid <new-oid>)\n    H: PKT-LINE(option forced-update)\n    H: ... ...\n    H: flush-pkt\n\nAfter receiving a command, the hook will execute the command, and may\ncreate/update different reference.  For example, a command for a pseudo\nreference \"refs/for/master/topic\" may create/update different reference\nsuch as \"refs/pull/123/head\".  The alternate reference name and other\nstatus are given in option lines.\n\nThe list of commands returned from \"proc-receive\" will replace the\nrelevant commands that are sent from user to \"receive-pack\", and\n\"receive-pack\" will continue to run the \"execute_commands\" function and\nother routines.  Finally, the result of the execution of these commands\nwill be reported to end user.\n\nThe reporting function from \"receive-pack\" to \"send-pack\" will be\nextended in latter commit just like what the \"proc-receive\" hook reports\nto \"receive-pack\".\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Makefile                                      |   1 +\n builtin/receive-pack.c                        | 351 ++++++++++++++++--\n remote.h                                      |  14 +\n t/helper/test-proc-receive.c                  | 174 +++++++++\n t/helper/test-tool.c                          |   1 +\n t/helper/test-tool.h                          |   1 +\n t/t5411/test-0011-no-hook-error.sh            |  64 ++++\n t/t5411/test-0012-no-hook-error--porcelain.sh |  66 ++++\n t/t5411/test-0013-bad-protocol.sh             | 217 +++++++++++\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 160 ++++++++\n t/t5411/test-0020-report-ng.sh                |  67 ++++\n t/t5411/test-0021-report-ng--porcelain.sh     |  69 ++++\n t/t5411/test-0022-report-unexpect-ref.sh      |  45 +++\n ...est-0023-report-unexpect-ref--porcelain.sh |  46 +++\n t/t5411/test-0024-report-unknown-ref.sh       |  34 ++\n ...test-0025-report-unknown-ref--porcelain.sh |  35 ++\n t/t5411/test-0026-push-options.sh             |  79 ++++\n t/t5411/test-0027-push-options--porcelain.sh  |  82 ++++\n t/t5411/test-0030-report-ok.sh                |  35 ++\n t/t5411/test-0031-report-ok--porcelain.sh     |  36 ++\n t/t5411/test-0032-report-with-options.sh      | 256 +++++++++++++\n ...est-0033-report-with-options--porcelain.sh | 265 +++++++++++++\n t/t5411/test-0034-report-ft.sh                |  44 +++\n t/t5411/test-0035-report-ft--porcelain.sh     |  45 +++\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 159 ++++++++\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 162 ++++++++\n t/t5411/test-0038-report-mixed-refs.sh        |  89 +++++\n .../test-0039-report-mixed-refs--porcelain.sh |  91 +++++\n 28 files changed, 2657 insertions(+), 31 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100644 t/t5411/test-0011-no-hook-error.sh\n create mode 100644 t/t5411/test-0012-no-hook-error--porcelain.sh\n create mode 100644 t/t5411/test-0013-bad-protocol.sh\n create mode 100644 t/t5411/test-0014-bad-protocol--porcelain.sh\n create mode 100644 t/t5411/test-0020-report-ng.sh\n create mode 100644 t/t5411/test-0021-report-ng--porcelain.sh\n create mode 100644 t/t5411/test-0022-report-unexpect-ref.sh\n create mode 100644 t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n create mode 100644 t/t5411/test-0024-report-unknown-ref.sh\n create mode 100644 t/t5411/test-0025-report-unknown-ref--porcelain.sh\n create mode 100644 t/t5411/test-0026-push-options.sh\n create mode 100644 t/t5411/test-0027-push-options--porcelain.sh\n create mode 100644 t/t5411/test-0030-report-ok.sh\n create mode 100644 t/t5411/test-0031-report-ok--porcelain.sh\n create mode 100644 t/t5411/test-0032-report-with-options.sh\n create mode 100644 t/t5411/test-0033-report-with-options--porcelain.sh\n create mode 100644 t/t5411/test-0034-report-ft.sh\n create mode 100644 t/t5411/test-0035-report-ft--porcelain.sh\n create mode 100644 t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n create mode 100644 t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n create mode 100644 t/t5411/test-0038-report-mixed-refs.sh\n create mode 100644 t/t5411/test-0039-report-mixed-refs--porcelain.sh\n\ndiff --git a/Makefile b/Makefile\nindex 3d3a39fc19..607aeabeee 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -722,6 +722,7 @@ TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\n+TEST_BUILTINS_OBJS += test-proc-receive.o\n TEST_BUILTINS_OBJS += test-progress.o\n TEST_BUILTINS_OBJS += test-reach.o\n TEST_BUILTINS_OBJS += test-read-cache.o\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex ea3d0f01af..717bebd3e1 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -309,11 +309,14 @@ static void write_head_info(void)\n \tpacket_flush(1);\n }\n \n+#define RUN_PROC_RECEIVE_SCHEDULED\t1\n+#define RUN_PROC_RECEIVE_RETURNED\t2\n struct command {\n \tstruct command *next;\n-\tconst char *error_string;\n+\tstruct ref_push_report report;\n \tunsigned int skip_update:1,\n-\t\t     did_not_exist:1;\n+\t\t     did_not_exist:1,\n+\t\t     run_proc_receive:2;\n \tint index;\n \tstruct object_id old_oid;\n \tstruct object_id new_oid;\n@@ -774,7 +777,7 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n \tstruct command *cmd = state->cmd;\n \n \twhile (cmd &&\n-\t       state->skip_broken && (cmd->error_string || cmd->did_not_exist))\n+\t       state->skip_broken && (cmd->report.error_message || cmd->did_not_exist))\n \t\tcmd = cmd->next;\n \tif (!cmd)\n \t\treturn -1; /* EOF */\n@@ -839,6 +842,268 @@ static int run_update_hook(struct command *cmd)\n \treturn finish_command(&proc);\n }\n \n+static struct command *find_command_by_refname(const struct command *list,\n+\t\t\t\t\t       const char *refname)\n+{\n+\tfor (; list; list = list->next)\n+\t\tif (!strcmp(list->ref_name, refname))\n+\t\t\treturn (struct command *)list;\n+\treturn NULL;\n+}\n+\n+static int read_proc_receive_report(struct packet_reader *reader,\n+\t\t\t\t    struct command *commands,\n+\t\t\t\t    struct strbuf *errmsg)\n+{\n+\tstruct command *cmd;\n+\tstruct command *hint = NULL;\n+\tint code = 0;\n+\tint new_options = 1;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *head;\n+\t\tconst char *refname;\n+\t\tchar *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\thead = reader->line;\n+\t\tp = strchr(head, ' ');\n+\t\tif (!p) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported incomplete status line: '%s'\\n\", head);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\t*p++ = '\\0';\n+\t\tif (!strcmp(head, \"option\")) {\n+\t\t\tstruct ref_push_report_options *options;\n+\t\t\tconst char *key, *val;\n+\n+\t\t\tif (!hint) {\n+\t\t\t       if (new_options) {\n+\t\t\t\t\tstrbuf_addstr(errmsg, \"proc-receive reported 'option' without a matching 'ok/ng' directive\\n\");\n+\t\t\t\t\tnew_options = 0;\n+\t\t\t\t}\n+\t\t\t\tcode = -1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\toptions = hint->report.options;\n+\t\t\twhile (options && options->next)\n+\t\t\t\toptions = options->next;\n+\t\t\tif (new_options) {\n+\t\t\t\tif (!options) {\n+\t\t\t\t\thint->report.options = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\t\toptions = hint->report.options;\n+\t\t\t\t} else {\n+\t\t\t\t\toptions->next = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\t\toptions = options->next;\n+\t\t\t\t}\n+\t\t\t\tnew_options = 0;\n+\t\t\t}\n+\t\t\tassert(options);\n+\t\t\tkey = p;\n+\t\t\tp = strchr(key, ' ');\n+\t\t\tif (p)\n+\t\t\t\t*p++ = '\\0';\n+\t\t\tval = p;\n+\t\t\tif (!strcmp(key, \"refname\"))\n+\t\t\t\toptions->ref_name = xstrdup_or_null(val);\n+\t\t\telse if (!strcmp(key, \"old-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &old_oid, &val))\n+\t\t\t\toptions->old_oid = oiddup(&old_oid);\n+\t\t\telse if (!strcmp(key, \"new-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &new_oid, &val))\n+\t\t\t\toptions->new_oid = oiddup(&new_oid);\n+\t\t\telse if (!strcmp(key, \"forced-update\"))\n+\t\t\t\toptions->forced_update = 1;\n+\t\t\telse if (!strcmp(key, \"fall-through\"))\n+\t\t\t\t/* Fall through, let 'receive-pack' to execute it. */\n+\t\t\t\thint->run_proc_receive = 0;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\trefname = p;\n+\t\tp = strchr(refname, ' ');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n+\t\tif (strcmp(head, \"ok\") && strcmp(head, \"ng\")) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported bad status '%s' on ref '%s'\\n\",\n+\t\t\t\t    head, refname);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/* first try searching at our hint, falling back to all refs */\n+\t\tif (hint)\n+\t\t\thint = find_command_by_refname(hint, refname);\n+\t\tif (!hint)\n+\t\t\thint = find_command_by_refname(commands, refname);\n+\t\tif (!hint) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unknown ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!hint->run_proc_receive) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unexpected ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(head, \"ng\")) {\n+\t\t\tif (p)\n+\t\t\t\thint->report.error_message = xstrdup(p);\n+\t\t\telse\n+\t\t\t\thint->report.error_message = \"failed\";\n+\t\t\tcode = -1;\n+\t\t}\n+\t\tif (hint->run_proc_receive)\n+\t\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n+\t\tnew_options = 1;\n+\t}\n+\n+\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\tif (cmd->run_proc_receive && !cmd->report.error_message &&\n+\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {\n+\t\t    cmd->report.error_message = \"proc-receive failed to report status\";\n+\t\t    code = -1;\n+\t\t}\n+\treturn code;\n+}\n+\n+static int run_proc_receive_hook(struct command *commands,\n+\t\t\t\t const struct string_list *push_options)\n+{\n+\tstruct child_process proc = CHILD_PROCESS_INIT;\n+\tstruct async muxer;\n+\tstruct command *cmd;\n+\tconst char *argv[2];\n+\tstruct packet_reader reader;\n+\tstruct strbuf cap = STRBUF_INIT;\n+\tstruct strbuf errmsg = STRBUF_INIT;\n+\tint hook_use_push_options = 0;\n+\tint version = 0;\n+\tint code;\n+\n+\targv[0] = find_hook(\"proc-receive\");\n+\tif (!argv[0]) {\n+\t\trp_error(\"cannot find hook 'proc-receive'\");\n+\t\treturn -1;\n+\t}\n+\targv[1] = NULL;\n+\n+\tproc.argv = argv;\n+\tproc.in = -1;\n+\tproc.out = -1;\n+\tproc.trace2_hook_name = \"proc-receive\";\n+\n+\tif (use_sideband) {\n+\t\tmemset(&muxer, 0, sizeof(muxer));\n+\t\tmuxer.proc = copy_to_sideband;\n+\t\tmuxer.in = -1;\n+\t\tcode = start_async(&muxer);\n+\t\tif (code)\n+\t\t\treturn code;\n+\t\tproc.err = muxer.in;\n+\t} else {\n+\t\tproc.err = 0;\n+\t}\n+\n+\tcode = start_command(&proc);\n+\tif (code) {\n+\t\tif (use_sideband)\n+\t\t\tfinish_async(&muxer);\n+\t\treturn code;\n+\t}\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\n+\t/* Version negotiaton */\n+\tpacket_reader_init(&reader, proc.out, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_GENTLE_ON_EOF);\n+\tif (use_atomic)\n+\t\tstrbuf_addstr(&cap, \" atomic\");\n+\tif (use_push_options)\n+\t\tstrbuf_addstr(&cap, \" push-options\");\n+\tif (cap.len) {\n+\t\tpacket_write_fmt(proc.in, \"version=1%c%s\\n\", '\\0', cap.buf + 1);\n+\t\tstrbuf_release(&cap);\n+\t} else {\n+\t\tpacket_write_fmt(proc.in, \"version=1\\n\");\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader.pktlen > 8 && starts_with(reader.line, \"version=\")) {\n+\t\t\tversion = atoi(reader.line + 8);\n+\t\t\tlinelen = strlen(reader.line);\n+\t\t\tif (linelen < reader.pktlen) {\n+\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\thook_use_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (version != 1) {\n+\t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n+\t\t\t    version);\n+\t\tcode = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\t/* Send commands */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->report.error_message)\n+\t\t\tcontinue;\n+\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n+\t\t\t\t oid_to_hex(&cmd->old_oid),\n+\t\t\t\t oid_to_hex(&cmd->new_oid),\n+\t\t\t\t cmd->ref_name);\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\t/* Send push options */\n+\tif (hook_use_push_options) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tfor_each_string_list_item(item, push_options)\n+\t\t\tpacket_write_fmt(proc.in, \"%s\", item->string);\n+\t\tpacket_flush(proc.in);\n+\t}\n+\n+\t/* Read result from proc-receive */\n+\tcode = read_proc_receive_report(&reader, commands, &errmsg);\n+\n+cleanup:\n+\tclose(proc.in);\n+\tclose(proc.out);\n+\tif (use_sideband)\n+\t\tfinish_async(&muxer);\n+\tif (finish_command(&proc))\n+\t\tcode = -1;\n+\tif (errmsg.len >0) {\n+\t\tchar *p = errmsg.buf;\n+\n+\t\tp += errmsg.len - 1;\n+\t\tif (*p == '\\n')\n+\t\t\t*p = '\\0';\n+\t\trp_error(\"%s\", errmsg.buf);\n+\t\tstrbuf_release(&errmsg);\n+\t}\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn code;\n+}\n+\n static char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n@@ -1202,7 +1467,7 @@ static void run_update_post_hook(struct command *commands)\n \t\treturn;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (cmd->error_string || cmd->did_not_exist)\n+\t\tif (cmd->report.error_message || cmd->did_not_exist)\n \t\t\tcontinue;\n \t\tif (!proc.args.argc)\n \t\t\targv_array_push(&proc.args, hook);\n@@ -1236,7 +1501,7 @@ static void check_aliased_update_internal(struct command *cmd,\n \tif (!dst_name) {\n \t\trp_error(\"refusing update to broken symref '%s'\", cmd->ref_name);\n \t\tcmd->skip_update = 1;\n-\t\tcmd->error_string = \"broken symref\";\n+\t\tcmd->report.error_message = \"broken symref\";\n \t\treturn;\n \t}\n \tdst_name = strip_namespace(dst_name);\n@@ -1263,7 +1528,7 @@ static void check_aliased_update_internal(struct command *cmd,\n \t\t find_unique_abbrev(&dst_cmd->old_oid, DEFAULT_ABBREV),\n \t\t find_unique_abbrev(&dst_cmd->new_oid, DEFAULT_ABBREV));\n \n-\tcmd->error_string = dst_cmd->error_string =\n+\tcmd->report.error_message = dst_cmd->report.error_message =\n \t\t\"inconsistent aliased update\";\n }\n \n@@ -1292,7 +1557,7 @@ static void check_aliased_updates(struct command *commands)\n \tstring_list_sort(&ref_list);\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!cmd->error_string)\n+\t\tif (!cmd->report.error_message)\n \t\t\tcheck_aliased_update(cmd, &ref_list);\n \t}\n \n@@ -1329,7 +1594,7 @@ static void set_connectivity_errors(struct command *commands,\n \t\t\t\t     &opt))\n \t\t\tcontinue;\n \n-\t\tcmd->error_string = \"missing necessary objects\";\n+\t\tcmd->report.error_message = \"missing necessary objects\";\n \t}\n }\n \n@@ -1368,7 +1633,7 @@ static void reject_updates_to_hidden(struct command *commands)\n \tprefix_len = refname_full.len;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (cmd->error_string)\n+\t\tif (cmd->report.error_message)\n \t\t\tcontinue;\n \n \t\tstrbuf_setlen(&refname_full, prefix_len);\n@@ -1377,9 +1642,9 @@ static void reject_updates_to_hidden(struct command *commands)\n \t\tif (!ref_is_hidden(cmd->ref_name, refname_full.buf))\n \t\t\tcontinue;\n \t\tif (is_null_oid(&cmd->new_oid))\n-\t\t\tcmd->error_string = \"deny deleting a hidden ref\";\n+\t\t\tcmd->report.error_message = \"deny deleting a hidden ref\";\n \t\telse\n-\t\t\tcmd->error_string = \"deny updating a hidden ref\";\n+\t\t\tcmd->report.error_message = \"deny updating a hidden ref\";\n \t}\n \n \tstrbuf_release(&refname_full);\n@@ -1387,7 +1652,7 @@ static void reject_updates_to_hidden(struct command *commands)\n \n static int should_process_cmd(struct command *cmd)\n {\n-\treturn !cmd->error_string && !cmd->skip_update;\n+\treturn !cmd->report.error_message && !cmd->skip_update;\n }\n \n static void warn_if_skipped_connectivity_check(struct command *commands,\n@@ -1414,24 +1679,24 @@ static void execute_commands_non_atomic(struct command *commands,\n \tstruct strbuf err = STRBUF_INIT;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\ttransaction = ref_transaction_begin(&err);\n \t\tif (!transaction) {\n \t\t\trp_error(\"%s\", err.buf);\n \t\t\tstrbuf_reset(&err);\n-\t\t\tcmd->error_string = \"transaction failed to start\";\n+\t\t\tcmd->report.error_message = \"transaction failed to start\";\n \t\t\tcontinue;\n \t\t}\n \n-\t\tcmd->error_string = update(cmd, si);\n+\t\tcmd->report.error_message = update(cmd, si);\n \n-\t\tif (!cmd->error_string\n+\t\tif (!cmd->report.error_message\n \t\t    && ref_transaction_commit(transaction, &err)) {\n \t\t\trp_error(\"%s\", err.buf);\n \t\t\tstrbuf_reset(&err);\n-\t\t\tcmd->error_string = \"failed to update ref\";\n+\t\t\tcmd->report.error_message = \"failed to update ref\";\n \t\t}\n \t\tref_transaction_free(transaction);\n \t}\n@@ -1454,12 +1719,12 @@ static void execute_commands_atomic(struct command *commands,\n \t}\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n-\t\tcmd->error_string = update(cmd, si);\n+\t\tcmd->report.error_message = update(cmd, si);\n \n-\t\tif (cmd->error_string)\n+\t\tif (cmd->report.error_message)\n \t\t\tgoto failure;\n \t}\n \n@@ -1472,8 +1737,8 @@ static void execute_commands_atomic(struct command *commands,\n \n failure:\n \tfor (cmd = commands; cmd; cmd = cmd->next)\n-\t\tif (!cmd->error_string)\n-\t\t\tcmd->error_string = reported_error;\n+\t\tif (!cmd->report.error_message)\n+\t\t\tcmd->report.error_message = reported_error;\n \n cleanup:\n \tref_transaction_free(transaction);\n@@ -1490,10 +1755,11 @@ static void execute_commands(struct command *commands,\n \tstruct iterate_data data;\n \tstruct async muxer;\n \tint err_fd = 0;\n+\tint run_proc_receive = 0;\n \n \tif (unpacker_error) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n-\t\t\tcmd->error_string = \"unpacker error\";\n+\t\t\tcmd->report.error_message = \"unpacker error\";\n \t\treturn;\n \t}\n \n@@ -1519,10 +1785,25 @@ static void execute_commands(struct command *commands,\n \n \treject_updates_to_hidden(commands);\n \n+\t/*\n+\t * Try to find commands that have special prefix in their reference names,\n+\t * and mark them to run an external \"proc-receive\" hook later.\n+\t */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!should_process_cmd(cmd))\n+\t\t\tcontinue;\n+\n+\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n+\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n+\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\trun_proc_receive = 1;\n+\t\t}\n+\t}\n+\n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\t\tif (!cmd->error_string)\n-\t\t\t\tcmd->error_string = \"pre-receive hook declined\";\n+\t\t\tif (!cmd->report.error_message)\n+\t\t\t\tcmd->report.error_message = \"pre-receive hook declined\";\n \t\t}\n \t\treturn;\n \t}\n@@ -1533,8 +1814,8 @@ static void execute_commands(struct command *commands,\n \t */\n \tif (tmp_objdir_migrate(tmp_objdir) < 0) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\t\tif (!cmd->error_string)\n-\t\t\t\tcmd->error_string = \"unable to migrate objects to permanent storage\";\n+\t\t\tif (!cmd->report.error_message)\n+\t\t\t\tcmd->report.error_message = \"unable to migrate objects to permanent storage\";\n \t\t}\n \t\treturn;\n \t}\n@@ -1545,6 +1826,14 @@ static void execute_commands(struct command *commands,\n \tfree(head_name_to_free);\n \thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, NULL, NULL);\n \n+\tif (run_proc_receive &&\n+\t    run_proc_receive_hook(commands, push_options))\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tif (!cmd->report.error_message &&\n+\t\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED) &&\n+\t\t\t    (cmd->run_proc_receive || use_atomic))\n+\t\t\t\tcmd->report.error_message = \"fail to run proc-receive hook\";\n+\n \tif (use_atomic)\n \t\texecute_commands_atomic(commands, si);\n \telse\n@@ -1906,7 +2195,7 @@ static void update_shallow_info(struct command *commands,\n \t\tif (is_null_oid(&cmd->new_oid))\n \t\t\tcontinue;\n \t\tif (ref_status[cmd->index]) {\n-\t\t\tcmd->error_string = \"shallow update not allowed\";\n+\t\t\tcmd->report.error_message = \"shallow update not allowed\";\n \t\t\tcmd->skip_update = 1;\n \t\t}\n \t}\n@@ -1921,12 +2210,12 @@ static void report(struct command *commands, const char *unpack_status)\n \tpacket_buf_write(&buf, \"unpack %s\\n\",\n \t\t\t unpack_status ? unpack_status : \"ok\");\n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!cmd->error_string)\n+\t\tif (!cmd->report.error_message)\n \t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n \t\t\t\t\t cmd->ref_name);\n \t\telse\n \t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n-\t\t\t\t\t cmd->ref_name, cmd->error_string);\n+\t\t\t\t\t cmd->ref_name, cmd->report.error_message);\n \t}\n \tpacket_buf_flush(&buf);\n \n@@ -2030,7 +2319,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tif (!check_cert_push_options(&push_options)) {\n \t\t\tstruct command *cmd;\n \t\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n-\t\t\t\tcmd->error_string = \"inconsistent push options\";\n+\t\t\t\tcmd->report.error_message = \"inconsistent push options\";\n \t\t}\n \n \t\tprepare_shallow_info(&si, &shallow);\ndiff --git a/remote.h b/remote.h\nindex 11d8719b58..6dbc7e0559 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -93,6 +93,20 @@ int for_each_remote(each_remote_fn fn, void *priv);\n \n int remote_has_url(struct remote *remote, const char *url);\n \n+struct ref_push_report_options {\n+\tconst char *ref_name;\n+\tstruct object_id *old_oid;\n+\tstruct object_id *new_oid;\n+\tunsigned int forced_update:1;\n+\tstruct ref_push_report_options *next;\n+};\n+\n+struct ref_push_report {\n+\tconst char *message;\n+\tconst char *error_message;\n+\tstruct ref_push_report_options *options;\n+};\n+\n struct ref {\n \tstruct ref *next;\n \tstruct object_id old_oid;\ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nnew file mode 100644\nindex 0000000000..2d619aeaad\n--- /dev/null\n+++ b/t/helper/test-proc-receive.c\n@@ -0,0 +1,174 @@\n+#include \"cache.h\"\n+#include \"connect.h\"\n+#include \"parse-options.h\"\n+#include \"pkt-line.h\"\n+#include \"sigchain.h\"\n+#include \"test-tool.h\"\n+\n+static const char *proc_receive_usage[] = {\n+\t\"test-tool proc-receive [<options>...]\",\n+\tNULL\n+};\n+\n+static int die_version = 0;\n+static int die_readline = 0;\n+static int no_push_options = 0;\n+static int use_atomic = 0;\n+static int use_push_options = 0;\n+static int verbose = 0;\n+static int version = 1;\n+static struct string_list returns = STRING_LIST_INIT_NODUP;\n+\n+struct command {\n+\tstruct command *next;\n+\tconst char *error_string;\n+\tunsigned int skip_update:1,\n+\t\t     did_not_exist:1;\n+\tint index;\n+\tstruct object_id old_oid;\n+\tstruct object_id new_oid;\n+\tchar ref_name[FLEX_ARRAY]; /* more */\n+};\n+\n+static void proc_receive_verison(struct packet_reader *reader) {\n+\tint server_version = 0;\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader->pktlen > 8 && starts_with(reader->line, \"version=\")) {\n+\t\t\tserver_version = atoi(reader->line+8);\n+\t\t\tlinelen = strlen(reader->line);\n+\t\t\tif (linelen < reader->pktlen) {\n+\t\t\t\tconst char *feature_list = reader->line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"atomic\"))\n+\t\t\t\t\tuse_atomic= 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tuse_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (server_version != 1 || die_version)\n+\t\tdie(\"bad protocol version: %d\", server_version);\n+\n+\tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n+\t\t\t version, '\\0',\n+\t\t\t use_push_options && !no_push_options ? \"push-options\": \"\");\n+\tpacket_flush(1);\n+}\n+\n+static void proc_receive_read_commands(struct packet_reader *reader,\n+\t\t\t\t       struct command **commands)\n+{\n+\tstruct command **tail = commands;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tstruct command *cmd;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    die_readline)\n+\t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n+\t\t\t    reader->line);\n+\t\trefname = p;\n+\t\tFLEX_ALLOC_STR(cmd, ref_name, refname);\n+\t\toidcpy(&cmd->old_oid, &old_oid);\n+\t\toidcpy(&cmd->new_oid, &new_oid);\n+\n+\t\t*tail = cmd;\n+\t\ttail = &cmd->next;\n+\t}\n+}\n+\n+static void proc_receive_read_push_options(struct packet_reader *reader,\n+\t\t\t\t\t   struct string_list *options)\n+{\n+\n+\tif (no_push_options || !use_push_options)\n+\t       return;\n+\n+\twhile (1) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tstring_list_append(options, reader->line);\n+\t}\n+}\n+\n+int cmd__proc_receive(int argc, const char **argv)\n+{\n+\tstruct packet_reader reader;\n+\tstruct command *commands = NULL;\n+\tstruct string_list push_options = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"no-push-options\", &no_push_options,\n+\t\t\t \"disable push options\"),\n+\t\tOPT_BOOL(0, \"die-version\", &die_version,\n+\t\t\t \"die during version negotiation\"),\n+\t\tOPT_BOOL(0, \"die-readline\", &die_readline,\n+\t\t\t \"die when readline\"),\n+\t\tOPT_STRING_LIST('r', \"return\", &returns, \"old/new/ref/status/msg\",\n+\t\t\t\t\"return of results\"),\n+\t\tOPT__VERBOSE(&verbose, \"be verbose\"),\n+\t\tOPT_INTEGER('V', \"version\", &version,\n+\t\t\t    \"use this protocol version number\"),\n+\t\tOPT_END()\n+\t};\n+\n+\targc = parse_options(argc, argv, \"test-tools\", options, proc_receive_usage, 0);\n+\tif (argc > 0)\n+\t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n+\n+\tpacket_reader_init(&reader, 0, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\tproc_receive_verison(&reader);\n+\tproc_receive_read_commands(&reader, &commands);\n+\tproc_receive_read_push_options(&reader, &push_options);\n+\n+\tif (verbose) {\n+\t\tstruct command *cmd;\n+\n+\t\tif (use_push_options || use_atomic)\n+\t\t\tfprintf(stderr, \"proc-receive:%s%s\\n\",\n+\t\t\t\tuse_atomic? \" atomic\": \"\",\n+\t\t\t\tuse_push_options ? \" push_options\": \"\");\n+\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tfprintf(stderr, \"proc-receive< %s %s %s\\n\",\n+\t\t\t\toid_to_hex(&cmd->old_oid),\n+\t\t\t\toid_to_hex(&cmd->new_oid),\n+\t\t\t\tcmd->ref_name);\n+\n+\t\tif (push_options.nr > 0)\n+\t\t\tfor_each_string_list_item(item, &push_options)\n+\t\t\t\tfprintf(stderr, \"proc-receive< %s\\n\", item->string);\n+\n+\t\tif (returns.nr)\n+\t\t\tfor_each_string_list_item(item, &returns)\n+\t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n+\t}\n+\n+\tif (returns.nr)\n+\t\tfor_each_string_list_item(item, &returns)\n+\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n+\tpacket_flush(1);\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 590b2efca7..a0d3966b29 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -46,6 +46,7 @@ static struct test_cmd cmds[] = {\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\n+\t{ \"proc-receive\", cmd__proc_receive},\n \t{ \"progress\", cmd__progress },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex ddc8e990e9..07034d3f38 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -35,6 +35,7 @@ int cmd__parse_pathspec_file(int argc, const char** argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\n+int cmd__proc_receive(int argc, const char **argv);\n int cmd__progress(int argc, const char **argv);\n int cmd__reach(int argc, const char **argv);\n int cmd__read_cache(int argc, const char **argv);\ndiff --git a/t/t5411/test-0011-no-hook-error.sh b/t/t5411/test-0011-no-hook-error.sh\nnew file mode 100644\nindex 0000000000..bb6ec92a92\n--- /dev/null\n+++ b/t/t5411/test-0011-no-hook-error.sh\n@@ -0,0 +1,64 @@\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\t$B:master \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] <COMMIT-B> -> master (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0012-no-hook-error--porcelain.sh b/t/t5411/test-0012-no-hook-error--porcelain.sh\nnew file mode 100644\nindex 0000000000..4814f74dc2\n--- /dev/null\n+++ b/t/t5411/test-0012-no-hook-error--porcelain.sh\n@@ -0,0 +1,66 @@\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain --atomic origin \\\n+\t\t$B:master \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t!    <COMMIT-B>:refs/heads/master    [remote rejected] (fail to run proc-receive hook)\n+\t!    HEAD:refs/heads/next    [remote rejected] (fail to run proc-receive hook)\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh\nnew file mode 100644\nindex 0000000000..c5fe4cb37b\n--- /dev/null\n+++ b/t/t5411/test-0013-bad-protocol.sh\n@@ -0,0 +1,217 @@\n+test_expect_success \"setup proc-receive hook (unknown version, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\t# Check status report for git-push\n+\tsed -n \\\n+\t\t-e \"/^To / { p; n; p; }\" \\\n+\t\t<actual >actual-report &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual-report &&\n+\n+\t# Check error message from \"receive-pack\", but ignore unstable fatal error\n+\t# message (\"remote: fatal: the remote end hung up unexpectedly\") which\n+\t# is different from the remote HTTP server with different locale settings.\n+\tgrep \"^remote: error:\" <actual >actual-error &&\n+\tcat >expect <<-EOF &&\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tEOF\n+\ttest_cmp expect actual-error &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-version, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-version\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-version, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: fatal: bad protocol version: 1\n+\tremote: error: proc-receive version \"0\" is not supported\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-readline, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-readline\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-readline, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\tgrep \"remote: fatal: protocol error: expected \\\"old new ref\\\", got \\\"<ZERO-OID> <COMMIT-A> refs/for/master/topic\\\"\" actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok\n+\tremote: error: proc-receive reported incomplete status line: \"ok\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"xx refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> xx refs/for/master/topic\n+\tremote: error: proc-receive reported bad status \"xx\" on ref \"refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0014-bad-protocol--porcelain.sh b/t/t5411/test-0014-bad-protocol--porcelain.sh\nnew file mode 100644\nindex 0000000000..53b47b0185\n--- /dev/null\n+++ b/t/t5411/test-0014-bad-protocol--porcelain.sh\n@@ -0,0 +1,160 @@\n+test_expect_success \"setup proc-receive hook (unknown version, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\t# Check status report for git-push\n+\tsed -n \\\n+\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n+\t\t<actual >actual-report &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual-report &&\n+\n+\t# Check error message from \"receive-pack\", but ignore unstable fatal error\n+\t# message (\"remote: fatal: the remote end hung up unexpectedly\") which\n+\t# is different from the remote HTTP server with different locale settings.\n+\tgrep \"^remote: error:\" <actual >actual-error &&\n+\tcat >expect <<-EOF &&\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tEOF\n+\ttest_cmp expect actual-error &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok\n+\tremote: error: proc-receive reported incomplete status line: \"ok\"\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"xx refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> xx refs/for/master/topic\n+\tremote: error: proc-receive reported bad status \"xx\" on ref \"refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0020-report-ng.sh b/t/t5411/test-0020-report-ng.sh\nnew file mode 100644\nindex 0000000000..f726b7ca9c\n--- /dev/null\n+++ b/t/t5411/test-0020-report-ng.sh\n@@ -0,0 +1,67 @@\n+test_expect_success \"setup proc-receive hook (ng, no message, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic error msg\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0021-report-ng--porcelain.sh b/t/t5411/test-0021-report-ng--porcelain.sh\nnew file mode 100644\nindex 0000000000..fbf5569103\n--- /dev/null\n+++ b/t/t5411/test-0021-report-ng--porcelain.sh\n@@ -0,0 +1,69 @@\n+test_expect_success \"setup proc-receive hook (ng, no message, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (failed)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic error msg\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (error msg)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0022-report-unexpect-ref.sh b/t/t5411/test-0022-report-unexpect-ref.sh\nnew file mode 100644\nindex 0000000000..92a415b929\n--- /dev/null\n+++ b/t/t5411/test-0022-report-unexpect-ref.sh\n@@ -0,0 +1,45 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: error: proc-receive reported status on unexpected ref: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\ndiff --git a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\nnew file mode 100644\nindex 0000000000..acbf93e40a\n--- /dev/null\n+++ b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n@@ -0,0 +1,46 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: error: proc-receive reported status on unexpected ref: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\ndiff --git a/t/t5411/test-0024-report-unknown-ref.sh b/t/t5411/test-0024-report-unknown-ref.sh\nnew file mode 100644\nindex 0000000000..c3946f329a\n--- /dev/null\n+++ b/t/t5411/test-0024-report-unknown-ref.sh\n@@ -0,0 +1,34 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: report unknown reference ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0025-report-unknown-ref--porcelain.sh b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\nnew file mode 100644\nindex 0000000000..d093b1a579\n--- /dev/null\n+++ b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\n@@ -0,0 +1,35 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: report unknown reference ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/a/b/c/my/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh\nnew file mode 100644\nindex 0000000000..d0c4da8b23\n--- /dev/null\n+++ b/t/t5411/test-0026-push-options.sh\n@@ -0,0 +1,79 @@\n+test_expect_success \"setup proc-receive hook and disable push-options ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions false &&\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"proc-receive: not support push options ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n+\t\tactual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"proc-receive: push with options ($PROTOCOL)\" '\n+\tgit -C workbench push \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\ndiff --git a/t/t5411/test-0027-push-options--porcelain.sh b/t/t5411/test-0027-push-options--porcelain.sh\nnew file mode 100644\nindex 0000000000..c89a1e7c57\n--- /dev/null\n+++ b/t/t5411/test-0027-push-options--porcelain.sh\n@@ -0,0 +1,82 @@\n+test_expect_success \"setup proc-receive hook and disable push-options ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions false &&\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"proc-receive: not support push options ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t--porcelain \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n+\t\tactual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"proc-receive: push with options ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push \\\n+\t\t--porcelain \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\ndiff --git a/t/t5411/test-0030-report-ok.sh b/t/t5411/test-0030-report-ok.sh\nnew file mode 100644\nindex 0000000000..44c99d3831\n--- /dev/null\n+++ b/t/t5411/test-0030-report-ok.sh\n@@ -0,0 +1,35 @@\n+test_expect_success \"setup proc-receive hook (ok, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0031-report-ok--porcelain.sh b/t/t5411/test-0031-report-ok--porcelain.sh\nnew file mode 100644\nindex 0000000000..3223b26184\n--- /dev/null\n+++ b/t/t5411/test-0031-report-ok--porcelain.sh\n@@ -0,0 +1,36 @@\n+test_expect_success \"setup proc-receive hook (ok, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nnew file mode 100644\nindex 0000000000..a743aa8018\n--- /dev/null\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -0,0 +1,256 @@\n+test_expect_success \"setup proc-receive hook (option without matching ok, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option without matching ok ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: error: proc-receive reported \"option\" without a matching \"ok/ng\" directive\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and forced-update, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and forced-update ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and old-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid and new-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (report with multiple rewrites, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/a/b/c/topic\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option forced-update\" \\\n+\t\t-r \"option new-oid $A\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/for/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/a/b/c/topic\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option forced-update\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/for/a/b/c/topic\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nnew file mode 100644\nindex 0000000000..439b97b06e\n--- /dev/null\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -0,0 +1,265 @@\n+test_expect_success \"setup proc-receive hook (option without matching ok, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option without matching ok ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: error: proc-receive reported \"option\" without a matching \"ok/ng\" directive\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and forced-update, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and forced-update ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and old-oid, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid and new-oid, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (report with multiple rewrites, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/a/b/c/topic\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option forced-update\" \\\n+\t\t-r \"option new-oid $A\"\n+\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/for/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/a/b/c/topic\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option forced-update\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/next/topic    [new reference]\n+\t*    HEAD:refs/for/a/b/c/topic    [new reference]\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0034-report-ft.sh b/t/t5411/test-0034-report-ft.sh\nnew file mode 100644\nindex 0000000000..aca2b0676c\n--- /dev/null\n+++ b/t/t5411/test-0034-report-ft.sh\n@@ -0,0 +1,44 @@\n+test_expect_success \"setup proc-receive hook (ft, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option fall-through\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(B)\n+test_expect_success \"proc-receive: fall throught, let receive-pack to execute ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option fall-through\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] <COMMIT-B> -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/for/master/topic\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             refs/for/master/topic(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/for/master/topic\n+'\ndiff --git a/t/t5411/test-0035-report-ft--porcelain.sh b/t/t5411/test-0035-report-ft--porcelain.sh\nnew file mode 100644\nindex 0000000000..30ffffb352\n--- /dev/null\n+++ b/t/t5411/test-0035-report-ft--porcelain.sh\n@@ -0,0 +1,45 @@\n+test_expect_success \"setup proc-receive hook (fall-through, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option fall-through\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(B)\n+test_expect_success \"proc-receive: fall throught, let receive-pack to execute ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\t$B:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option fall-through\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    <COMMIT-B>:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/for/master/topic\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             refs/for/master/topic(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/for/master/topic\n+'\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nnew file mode 100644\nindex 0000000000..12acf9ea1f\n--- /dev/null\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -0,0 +1,159 @@\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/23/123/1\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/2\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/23/123/1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/2\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\nnew file mode 100644\nindex 0000000000..f4a2c56e14\n--- /dev/null\n+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n@@ -0,0 +1,162 @@\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/23/123/1\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/2\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/23/123/1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/2\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nnew file mode 100644\nindex 0000000000..1dd6a97a97\n--- /dev/null\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -0,0 +1,89 @@\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/next/topic2\" \\\n+\t\t-r \"ng refs/for/next/topic1 fail to call Web API\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic2 \\\n+\t\tHEAD:refs/for/next/topic1 \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic3 \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n+\tremote: proc-receive> ok refs/for/next/topic2\n+\tremote: proc-receive> ng refs/for/next/topic1 fail to call Web API\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t * [new branch] HEAD -> bar\n+\t * [new branch] HEAD -> baz\n+\t * [new reference] HEAD -> refs/for/next/topic2\n+\t * [new branch] HEAD -> foo\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\t ! [remote rejected] HEAD -> refs/for/next/topic1 (fail to call Web API)\n+\t ! [remote rejected] HEAD -> refs/for/next/topic3 (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit update-ref -d refs/heads/foo &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+'\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nnew file mode 100644\nindex 0000000000..32ebf63dcf\n--- /dev/null\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -0,0 +1,91 @@\n+test_expect_success \"setup proc-receive hook ($PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/next/topic2\" \\\n+\t\t-r \"ng refs/for/next/topic1 fail to call Web API\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic2 \\\n+\t\tHEAD:refs/for/next/topic1 \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic3 \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n+\tremote: proc-receive> ok refs/for/next/topic2\n+\tremote: proc-receive> ng refs/for/next/topic1 fail to call Web API\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n+\t*    HEAD:refs/heads/bar    [new branch]\n+\t*    HEAD:refs/heads/baz    [new branch]\n+\t*    HEAD:refs/for/next/topic2    [new reference]\n+\t*    HEAD:refs/heads/foo    [new branch]\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\t!    HEAD:refs/for/next/topic1    [remote rejected] (fail to call Web API)\n+\t!    HEAD:refs/for/next/topic3    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit update-ref -d refs/heads/foo &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+\n+'\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"398046","messageId":"20200518094039.757-7-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200507161057.12690-1-worldhello.net@gmail.com","subject":"[PATCH v16 06/11] receive-pack: feed report options to post-receive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-18T09:40:34Z","receivedAt":"2020-05-18T09:41:01Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen commands are fed to the \"post-receive\" hook, report options will\nbe parsed and the real old-oid, new-oid, reference name will feed to\nthe \"post-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c                        | 29 ++++++++++++++++---\n t/t5411/once-0010-report-status-v1.sh         |  3 +-\n t/t5411/test-0032-report-with-options.sh      | 14 ++++-----\n ...est-0033-report-with-options--porcelain.sh | 14 ++++-----\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 11 +++++--\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 11 +++++--\n t/t5411/test-0038-report-mixed-refs.sh        |  2 +-\n .../test-0039-report-mixed-refs--porcelain.sh |  2 +-\n 8 files changed, 59 insertions(+), 27 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 5544d03718..e2478f34b8 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -776,17 +776,38 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n {\n \tstruct receive_hook_feed_state *state = state_;\n \tstruct command *cmd = state->cmd;\n+\tstatic struct ref_push_report_options *options = NULL;\n \n \twhile (cmd &&\n \t       state->skip_broken && (cmd->report.error_message || cmd->did_not_exist))\n \t\tcmd = cmd->next;\n \tif (!cmd)\n \t\treturn -1; /* EOF */\n+\tif (!bufp)\n+\t\treturn 0; /* OK, can feed something. */\n \tstrbuf_reset(&state->buf);\n-\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n-\t\t    oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),\n-\t\t    cmd->ref_name);\n-\tstate->cmd = cmd->next;\n+\tif (!options)\n+\t\toptions = cmd->report.options;\n+\tif (options) {\n+\t\tstruct object_id *old_oid;\n+\t\tstruct object_id *new_oid;\n+\t\tconst char *ref_name;\n+\n+\t\told_oid = options->old_oid ? options->old_oid : &cmd->old_oid;\n+\t\tnew_oid = options->new_oid ? options->new_oid : &cmd->new_oid;\n+\t\tref_name = options->ref_name ? options->ref_name : cmd->ref_name;\n+\t\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n+\t\t\t    oid_to_hex(old_oid), oid_to_hex(new_oid),\n+\t\t\t    ref_name);\n+\t\toptions = options->next;\n+\t\tif (!options)\n+\t\t\tstate->cmd = cmd->next;\n+\t} else {\n+\t\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n+\t\t\t    oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),\n+\t\t\t    cmd->ref_name);\n+\t\tstate->cmd = cmd->next;\n+\t}\n \tif (bufp) {\n \t\t*bufp = state->buf.buf;\n \t\t*sizep = state->buf.len;\ndiff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh\nindex f93d760cfd..4385628d06 100644\n--- a/t/t5411/once-0010-report-status-v1.sh\n+++ b/t/t5411/once-0010-report-status-v1.sh\n@@ -68,7 +68,8 @@ test_expect_success \"proc-receive: report status v1\" '\n \tpost-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n \tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n \tpost-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n-\tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2\n+\tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/changes/23/123/1\n+\tpost-receive< <COMMIT-B> <COMMIT-A> refs/for/changes/24/124/2\n \tEOF\n \ttest_cmp expect actual &&\n \ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nindex 3742ed3c3d..b77b78c49f 100644\n--- a/t/t5411/test-0032-report-with-options.sh\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -54,7 +54,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL)\" '\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n@@ -87,7 +87,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n@@ -121,7 +121,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL)\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t <OID-B>..<OID-A> HEAD -> refs/pull/123/head\n \tEOF\n@@ -153,7 +153,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL)\" '\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t <OID-B>..<OID-A> HEAD -> refs/for/master/topic\n \tEOF\n@@ -187,7 +187,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL)\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \tEOF\n@@ -237,9 +237,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n \tremote: proc-receive> option forced-update\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/pull/123/head\n \t * [new reference] HEAD -> refs/for/a/b/c/topic\ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nindex f18ba9f06f..1fe352b686 100644\n--- a/t/t5411/test-0033-report-with-options--porcelain.sh\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -55,7 +55,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL/porcelain)\"\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n@@ -90,7 +90,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n@@ -125,7 +125,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL/\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t     HEAD:refs/pull/123/head    <OID-B>..<OID-A>\n \tDone\n@@ -158,7 +158,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL/porcelain)\"\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t     HEAD:refs/for/master/topic    <OID-B>..<OID-A>\n \tDone\n@@ -193,7 +193,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL/\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \tDone\n@@ -245,9 +245,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porc\n \tremote: proc-receive> option forced-update\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/pull/123/head    [new reference]\n \t*    HEAD:refs/for/a/b/c/topic    [new reference]\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex 64662afdc6..27d58edd14 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -41,7 +41,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \t * [new reference] HEAD -> refs/changes/24/124/1\n@@ -101,7 +103,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/changes/24/124/1\n \t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n@@ -149,7 +153,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/changes/23/123/1\n \t <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2\ndiff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\nindex a7e29af0c2..77b5b22ed4 100644\n--- a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n@@ -41,7 +41,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \t*    HEAD:refs/changes/24/124/1    [new reference]\n@@ -102,7 +104,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/changes/24/124/1    [new reference]\n \t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n@@ -151,7 +155,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porc\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/changes/23/123/1    [new reference]\n \t     HEAD:refs/changes/24/124/2    <OID-A>..<OID-B>\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nindex 54bec4c026..a74a2cb449 100644\n--- a/t/t5411/test-0038-report-mixed-refs.sh\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -53,7 +53,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t <OID-A>..<OID-B> <COMMIT-B> -> master\n \t * [new branch] HEAD -> bar\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nindex 605b3cb5c9..e4baa13ea3 100644\n--- a/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -53,7 +53,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcel\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n \t*    HEAD:refs/heads/bar    [new branch]\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"398047","messageId":"20200518094039.757-8-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200507161057.12690-1-worldhello.net@gmail.com","subject":"[PATCH v16 07/11] refs.c: refactor to reuse ref_is_hidden()","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-18T09:40:35Z","receivedAt":"2020-05-18T09:41:01Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd new function `ref_is_matched()` to reuse `ref_is_hidden()`. Will use\nthis function for `receive-pack` to check commands with specific\nprefixes.\n\nTest case t5512 covered this change.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n refs.c | 13 +++++++++----\n refs.h |  1 +\n 2 files changed, 10 insertions(+), 4 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 224ff66c7b..76a91ac0f6 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1344,14 +1344,14 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti\n \treturn 0;\n }\n \n-int ref_is_hidden(const char *refname, const char *refname_full)\n+int ref_matches(struct string_list *match_refs, const char *refname, const char *refname_full)\n {\n \tint i;\n \n-\tif (!hide_refs)\n+\tif (!match_refs)\n \t\treturn 0;\n-\tfor (i = hide_refs->nr - 1; i >= 0; i--) {\n-\t\tconst char *match = hide_refs->items[i].string;\n+\tfor (i = match_refs->nr - 1; i >= 0; i--) {\n+\t\tconst char *match = match_refs->items[i].string;\n \t\tconst char *subject;\n \t\tint neg = 0;\n \t\tconst char *p;\n@@ -1377,6 +1377,11 @@ int ref_is_hidden(const char *refname, const char *refname_full)\n \treturn 0;\n }\n \n+int ref_is_hidden(const char *refname, const char *refname_full)\n+{\n+\treturn ref_matches(hide_refs, refname, refname_full);\n+}\n+\n const char *find_descendant_ref(const char *dirname,\n \t\t\t\tconst struct string_list *extras,\n \t\t\t\tconst struct string_list *skip)\ndiff --git a/refs.h b/refs.h\nindex a92d2c74c8..9907dd12b0 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -727,6 +727,7 @@ int parse_hide_refs_config(const char *var, const char *value, const char *);\n  * parameter always points to the full ref name.\n  */\n int ref_is_hidden(const char *, const char *);\n+int ref_matches(struct string_list *, const char *, const char *);\n \n enum ref_type {\n \tREF_TYPE_PER_WORKTREE,\t  /* refs inside refs/ but not shared       */\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"398048","messageId":"20200518094039.757-10-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200507161057.12690-1-worldhello.net@gmail.com","subject":"[PATCH v16 09/11] t5411: test updates of remote-tracking branches","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-18T09:40:37Z","receivedAt":"2020-05-18T09:41:03Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nIn order to test update of remote-tracking branches for special refs,\nadd new \"remote.origin.fetch\" settings and test cases.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 48 +++++++++++++++++++\n 1 file changed, 48 insertions(+)\n\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex 27d58edd14..8eec3a1ed8 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -1,3 +1,18 @@\n+test_expect_success \"setup git config for remote-tracking of special refs\" '\n+\t(\n+\t\tcd workbench &&\n+\t\tif ! git config --get-all remote.origin.fetch | grep refs/for/\n+\t\tthen\n+\t\t\tgit config --add remote.origin.fetch \\\n+\t\t\t\t\"+refs/for/*:refs/t/for/*\" &&\n+\t\t\tgit config --add remote.origin.fetch \\\n+\t\t\t\t\"+refs/pull/*:refs/t/pull/*\" &&\n+\t\t\tgit config --add remote.origin.fetch \\\n+\t\t\t\t\"+refs/changes/*:refs/t/changes/*\"\n+\t\tfi\n+\t)\n+'\n+\n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL)\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n@@ -58,6 +73,17 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \ttest_cmp expect actual\n '\n \n+test_expect_success \"proc-receive: check remote-tracking #1 ($PROTOCOL)\" '\n+\tgit -C workbench show-ref |\n+\t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/t/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C workbench update-ref -d refs/t/for/master/topic\n+'\n+\n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n@@ -120,6 +146,17 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \ttest_cmp expect actual\n '\n \n+test_expect_success \"proc-receive: check remote-tracking #2 ($PROTOCOL)\" '\n+\tgit -C workbench show-ref |\n+\t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/t/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C workbench update-ref -d refs/t/for/master/topic\n+'\n+\n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n@@ -167,3 +204,14 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n \tEOF\n \ttest_cmp expect actual\n '\n+\n+test_expect_success \"proc-receive: check remote-tracking #3 ($PROTOCOL)\" '\n+\tgit -C workbench show-ref |\n+\t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/t/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C workbench update-ref -d refs/t/for/master/topic\n+'\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"398049","messageId":"20200518094039.757-9-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200507161057.12690-1-worldhello.net@gmail.com","subject":"[PATCH v16 08/11] receive-pack: new config receive.procReceiveRefs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-18T09:40:36Z","receivedAt":"2020-05-18T09:41:03Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd a new multi-valued config variable \"receive.procReceiveRefs\"\nfor `receive-pack` command, like the follows:\n\n    git config --system --add receive.procReceiveRefs refs/for\n    git config --system --add receive.procReceiveRefs refs/drafts\n\nIf the specific prefix strings match the reference names of the commands\nwhich are sent from git client to `receive-pack`, these commands will be\nexecuted by an external hook (named \"proc-receive\"), instead of the\ninternal `execute_commands` function.\n\nFor example, if it is set to \"refs/for\", pushing to a reference such as\n\"refs/for/master\" will not create or update reference \"refs/for/master\",\nbut may create or update a pull request directly by running the hook\n\"proc-receive\".\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/config/receive.txt              |  14 +++\n builtin/receive-pack.c                        |  43 +++++--\n t/t5411/once-0010-report-status-v1.sh         |   4 +\n t/t5411/test-0010-proc-receive-settings.sh    |   7 ++\n t/t5411/test-0040-process-all-refs.sh         | 113 +++++++++++++++++\n .../test-0041-process-all-refs--porcelain.sh  | 114 ++++++++++++++++++\n 6 files changed, 288 insertions(+), 7 deletions(-)\n create mode 100644 t/t5411/test-0010-proc-receive-settings.sh\n create mode 100644 t/t5411/test-0040-process-all-refs.sh\n create mode 100644 t/t5411/test-0041-process-all-refs--porcelain.sh\n\ndiff --git a/Documentation/config/receive.txt b/Documentation/config/receive.txt\nindex 65f78aac37..e7b967feeb 100644\n--- a/Documentation/config/receive.txt\n+++ b/Documentation/config/receive.txt\n@@ -114,6 +114,20 @@ receive.hideRefs::\n \tAn attempt to update or delete a hidden ref by `git push` is\n \trejected.\n \n+receive.procReceiveRefs::\n+\tThis is a multi-valued variable that defines reference prefixes\n+\tto match the commands in `receive-pack`.  Commands matching the\n+\tprefixes will be executed by an external hook \"proc-receive\",\n+\tinstead of the internal `execute_commands` function.  If this\n+\tvariable is not defined, the \"proc-receive\" hook will never be\n+\tused, and all commands will be executed by the internal\n+\t`execute_commands` function.\n+\n+\tFor example, if this variable is set to \"refs/for\", pushing to\n+\treference such as \"refs/for/master\" will not create or update a\n+\treference named \"refs/for/master\", but may create or update a\n+\tpull request directly by running the hook \"proc-receive\".\n+\n receive.updateServerInfo::\n \tIf set to true, git-receive-pack will run git-update-server-info\n \tafter receiving data from git-push and updating refs.\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex e2478f34b8..136ff85688 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -78,6 +78,7 @@ static struct object_id push_cert_oid;\n static struct signature_check sigcheck;\n static const char *push_cert_nonce;\n static const char *cert_nonce_seed;\n+static struct string_list proc_receive_refs;\n \n static const char *NONCE_UNSOLICITED = \"UNSOLICITED\";\n static const char *NONCE_BAD = \"BAD\";\n@@ -230,6 +231,20 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (strcmp(var, \"receive.procreceiverefs\") == 0) {\n+\t\tchar *prefix;\n+\t\tint len;\n+\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tprefix = xstrdup(value);\n+\t\tlen = strlen(prefix);\n+\t\twhile (len && prefix[len - 1] == '/')\n+\t\t\tprefix[--len] = '\\0';\n+\t\tstring_list_append(&proc_receive_refs, prefix);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -1811,15 +1826,26 @@ static void execute_commands(struct command *commands,\n \t * Try to find commands that have special prefix in their reference names,\n \t * and mark them to run an external \"proc-receive\" hook later.\n \t */\n-\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n-\t\t\tcontinue;\n+\tif (proc_receive_refs.nr > 0) {\n+\t\tstruct strbuf refname_full = STRBUF_INIT;\n+\t\tsize_t prefix_len;\n+\n+\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n+\t\tprefix_len = refname_full.len;\n \n-\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n-\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n-\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n-\t\t\trun_proc_receive = 1;\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tif (!should_process_cmd(cmd))\n+\t\t\t\tcontinue;\n+\n+\t\t\tstrbuf_setlen(&refname_full, prefix_len);\n+\t\t\tstrbuf_addstr(&refname_full, cmd->ref_name);\n+\t\t\tif (ref_matches(&proc_receive_refs, cmd->ref_name, refname_full.buf)) {\n+\t\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\t\trun_proc_receive = 1;\n+\t\t\t}\n \t\t}\n+\n+\t\tstrbuf_release(&refname_full);\n \t}\n \n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n@@ -2320,6 +2346,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n+\tstring_list_init(&proc_receive_refs, 0);\n+\n \tpacket_trace_identity(\"receive-pack\");\n \n \targc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);\n@@ -2437,5 +2465,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \toid_array_clear(&shallow);\n \toid_array_clear(&ref);\n \tfree((void *)push_cert_nonce);\n+\tstring_list_clear(&proc_receive_refs, 0);\n \treturn 0;\n }\ndiff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh\nindex 4385628d06..cdafc8971f 100644\n--- a/t/t5411/once-0010-report-status-v1.sh\n+++ b/t/t5411/once-0010-report-status-v1.sh\n@@ -1,3 +1,7 @@\n+test_expect_success \"setup receive.procReceiveRefs\" '\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs/for\n+'\n+\n test_expect_success \"setup proc-receive hook\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\ndiff --git a/t/t5411/test-0010-proc-receive-settings.sh b/t/t5411/test-0010-proc-receive-settings.sh\nnew file mode 100644\nindex 0000000000..a36809927b\n--- /dev/null\n+++ b/t/t5411/test-0010-proc-receive-settings.sh\n@@ -0,0 +1,7 @@\n+test_expect_success \"add two receive.procReceiveRefs settings\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit config --add receive.procReceiveRefs refs/for &&\n+\t\tgit config --add receive.procReceiveRefs refs/review/\n+\t)\n+'\ndiff --git a/t/t5411/test-0040-process-all-refs.sh b/t/t5411/test-0040-process-all-refs.sh\nnew file mode 100644\nindex 0000000000..b07c999f53\n--- /dev/null\n+++ b/t/t5411/test-0040-process-all-refs.sh\n@@ -0,0 +1,113 @@\n+test_expect_success \"config receive.procReceiveRefs = refs ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config --unset-all receive.procReceiveRefs &&\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup upstream branches ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $B &&\n+\t\tgit update-ref refs/heads/foo $A &&\n+\t\tgit update-ref refs/heads/bar $A &&\n+\t\tgit update-ref refs/heads/baz $A\n+\t)\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/foo\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/bar\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"proc-receive: process all refs ($PROTOCOL)\" '\n+\tgit -C workbench push -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/foo\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/bar\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> bar\n+\t - [deleted] foo\n+\t + <OID-B>...<OID-A> HEAD -> master (forced update)\n+\t <OID-A>..<OID-B> HEAD -> refs/pull/123/head\n+\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             bar(A)  baz(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+'\ndiff --git a/t/t5411/test-0041-process-all-refs--porcelain.sh b/t/t5411/test-0041-process-all-refs--porcelain.sh\nnew file mode 100644\nindex 0000000000..0dd9824616\n--- /dev/null\n+++ b/t/t5411/test-0041-process-all-refs--porcelain.sh\n@@ -0,0 +1,114 @@\n+test_expect_success \"config receive.procReceiveRefs = refs ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" config --unset-all receive.procReceiveRefs &&\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup upstream branches ($PROTOCOL/porcelain)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $B &&\n+\t\tgit update-ref refs/heads/foo $A &&\n+\t\tgit update-ref refs/heads/bar $A &&\n+\t\tgit update-ref refs/heads/baz $A\n+\t)\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook ($PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/foo\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/bar\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"proc-receive: process all refs ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/foo\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/bar\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/bar    <OID-A>..<OID-B>\n+\t-    :refs/heads/foo    [deleted]\n+\t+    HEAD:refs/heads/master    <OID-B>...<OID-A> (forced update)\n+\t     HEAD:refs/pull/123/head    <OID-A>..<OID-B>\n+\t+    HEAD:refs/pull/124/head    <OID-B>...<OID-A> (forced update)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             bar(A)  baz(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+'\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"398051","messageId":"20200518094039.757-11-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200507161057.12690-1-worldhello.net@gmail.com","subject":"[PATCH v16 10/11] transport: parse report options for tracking refs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-18T09:40:38Z","receivedAt":"2020-05-18T09:41:06Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing a pseudo reference (such as \"refs/for/master/topic\"), may\ncreate or update one or more references.  The real names of the\nreferences will be stored in the report options.  Parse report options\nto create or update remote-tracking branches properly.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 22 +++++++---\n transport.c                                   | 40 ++++++++++++++-----\n 2 files changed, 46 insertions(+), 16 deletions(-)\n\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex 8eec3a1ed8..73283d81e8 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -78,10 +78,14 @@ test_expect_success \"proc-receive: check remote-tracking #1 ($PROTOCOL)\" '\n \t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n-\t<COMMIT-A> refs/t/for/master/topic\n+\t<COMMIT-A> refs/t/changes/24/124/1\n+\t<COMMIT-B> refs/t/changes/25/125/1\n+\t<COMMIT-B> refs/t/for/master/topic\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C workbench update-ref -d refs/t/for/master/topic\n+\tgit -C workbench update-ref -d refs/t/for/master/topic &&\n+\tgit -C workbench update-ref -d refs/t/changes/24/124/1 &&\n+\tgit -C workbench update-ref -d refs/t/changes/25/125/1\n '\n \n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)\" '\n@@ -151,10 +155,14 @@ test_expect_success \"proc-receive: check remote-tracking #2 ($PROTOCOL)\" '\n \t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n-\t<COMMIT-A> refs/t/for/master/topic\n+\t<COMMIT-A> refs/t/changes/24/124/1\n+\t<COMMIT-A> refs/t/changes/25/125/1\n+\t<COMMIT-B> refs/t/for/master/topic\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C workbench update-ref -d refs/t/for/master/topic\n+\tgit -C workbench update-ref -d refs/t/for/master/topic &&\n+\tgit -C workbench update-ref -d refs/t/changes/24/124/1 &&\n+\tgit -C workbench update-ref -d refs/t/changes/25/125/1\n '\n \n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)\" '\n@@ -210,8 +218,10 @@ test_expect_success \"proc-receive: check remote-tracking #3 ($PROTOCOL)\" '\n \t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n-\t<COMMIT-A> refs/t/for/master/topic\n+\t<COMMIT-A> refs/t/changes/23/123/1\n+\t<COMMIT-B> refs/t/changes/24/124/2\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C workbench update-ref -d refs/t/for/master/topic\n+\tgit -C workbench update-ref -d refs/t/changes/24/124/1 &&\n+\tgit -C workbench update-ref -d refs/t/changes/25/125/2\n '\ndiff --git a/transport.c b/transport.c\nindex 5baa30dd74..cd01f82f5c 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -437,28 +437,48 @@ int transport_refs_pushed(struct ref *ref)\n \treturn 0;\n }\n \n-void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose)\n+static void update_tracking_ref(struct remote *remote, char *refname,\n+\t\t\t\tstruct object_id *new_oid, int deletion,\n+\t\t\t\tint verbose)\n {\n \tstruct refspec_item rs;\n \n-\tif (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE)\n-\t\treturn;\n-\n-\trs.src = ref->name;\n+\trs.src = refname;\n \trs.dst = NULL;\n-\n \tif (!remote_find_tracking(remote, &rs)) {\n \t\tif (verbose)\n \t\t\tfprintf(stderr, \"updating local tracking ref '%s'\\n\", rs.dst);\n-\t\tif (ref->deletion) {\n+\t\tif (deletion)\n \t\t\tdelete_ref(NULL, rs.dst, NULL, 0);\n-\t\t} else\n-\t\t\tupdate_ref(\"update by push\", rs.dst, &ref->new_oid,\n-\t\t\t\t   NULL, 0, 0);\n+\t\telse\n+\t\t\tupdate_ref(\"update by push\", rs.dst, new_oid,\n+\t\t\t\tNULL, 0, 0);\n \t\tfree(rs.dst);\n \t}\n }\n \n+void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose)\n+{\n+\tchar *refname;\n+\tstruct object_id *new_oid;\n+\tstruct ref_push_report_options *options;\n+\n+\tif (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE)\n+\t\treturn;\n+\n+\toptions = ref->report.options;\n+\tif (!options)\n+\t\tupdate_tracking_ref(remote, ref->name, &ref->new_oid,\n+\t\t\t\t    ref->deletion, verbose);\n+\telse\n+\t\tfor (; options; options = options->next) {\n+\t\t\trefname = options->ref_name ? (char *)options->ref_name : ref->name;\n+\t\t\tnew_oid = options->new_oid ? options->new_oid : &ref->new_oid;\n+\t\t\tupdate_tracking_ref(remote, refname, new_oid,\n+\t\t\t\t\t    is_null_oid(new_oid), verbose);\n+\t\t}\n+}\n+\n static void print_ref_status(char flag, const char *summary,\n \t\t\t     struct ref *to, struct ref *from, const char *msg,\n \t\t\t     struct ref_push_report_options *options,\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"398052","messageId":"20200518094039.757-12-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200507161057.12690-1-worldhello.net@gmail.com","subject":"[PATCH v16 11/11] doc: add documentation for the proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-18T09:40:39Z","receivedAt":"2020-05-18T09:41:08Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\n\"git receive-pack\" that accepts requests by \"git push\" learned to\noutsource some of the ref updates to the new \"proc-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/githooks.txt | 62 ++++++++++++++++++++++++++++++++++++++\n 1 file changed, 62 insertions(+)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 81f2a87e88..d66154f606 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -333,6 +333,68 @@ The default 'update' hook, when enabled--and with\n `hooks.allowunannotated` config option unset or set to false--prevents\n unannotated tags to be pushed.\n \n+[[proc-receive]]\n+proc-receive\n+~~~~~~~~~~~~\n+\n+This hook is invoked by linkgit:git-receive-pack[1].  If the server has\n+set the multi-valued config variable `receive.procReceiveRefs`, and the\n+commands sent to 'receive-pack' have matching reference names, these\n+commands will be executed by this hook, instead of by the internal\n+`execute_commands()` function.  This hook is responsible for updating\n+the relevant references and reporting the results back to 'receive-pack'.\n+\n+This hook executes once for the receive operation.  It takes no\n+arguments, but uses a pkt-line format protocol to communicate with\n+'receive-pack' to read commands, push-options and send results.  In the\n+following example for the protocol, the letter 'S' stands for\n+'receive-pack' and the letter 'H' stands for this hook.\n+\n+    # Version and features negotiation.\n+    S: PKT-LINE(version=1\\0push-options atomic...)\n+    S: flush-pkt\n+    H: PKT-LINE(version=1\\0push-options...)\n+    H: flush-pkt\n+\n+    # Send commands from server to the hook.\n+    S: PKT-LINE(<old-oid> <new-oid> <ref>)\n+    S: ... ...\n+    S: flush-pkt\n+    # Send push-options only if the 'push-options' feature is enabled.\n+    S: PKT-LINE(push-option)\n+    S: ... ...\n+    S: flush-pkt\n+\n+    # Receive result from the hook.\n+    # OK, run this command successfully.\n+    H: PKT-LINE(ok <ref>)\n+    # NO, I reject it.\n+    H: PKT-LINE(ng <ref> <reason>)\n+    # Fall through, let 'receive-pack' to execute it.\n+    H: PKT-LINE(ok <ref>)\n+    H: PKT-LINE(option fall-through)\n+    # OK, but has an alternate reference.  The alternate reference name\n+    # and other status can be given in options\n+    H: PKT-LINE(ok <ref>)\n+    H: PKT-LINE(option refname <refname>)\n+    H: PKT-LINE(option old-oid <old-oid>)\n+    H: PKT-LINE(option new-oid <new-oid>)\n+    H: PKT-LINE(option forced-update)\n+    H: ... ...\n+    H: flush-pkt\n+\n+Each command for the 'proc-receive' hook may point to a pseudo-reference\n+and always has a zero-old as its old-oid, while the 'proc-receive' hook\n+may update an alternate reference and the alternate reference may exist\n+already with a non-zero old-oid.  For this case, this hook will use\n+\"option\" directives to report extended attributes for the reference given\n+by the above \"ok/ng\" directive.\n+\n+The report of the commands of this hook should have the same order as\n+the input.  The exit status of the 'proc-receive' hook only determines\n+the success or failure of the group of commands sent to it, unless\n+atomic push is in use.\n+\n [[post-receive]]\n post-receive\n ~~~~~~~~~~~~\n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"398053","messageId":"20200518094039.757-6-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200507161057.12690-1-worldhello.net@gmail.com","subject":"[PATCH v16 05/11] doc: add document for capability report-status-v2","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-05-18T09:40:33Z","receivedAt":"2020-05-18T09:41:10Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd ABNF notation for capability 'report-status-v2' which extends\ncapability 'report-status' by adding additional option lines.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/technical/pack-protocol.txt     | 43 +++++++++++++++++--\n .../technical/protocol-capabilities.txt       | 17 ++++++--\n 2 files changed, 54 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt\nindex d5ce4eea8a..7547b9ca89 100644\n--- a/Documentation/technical/pack-protocol.txt\n+++ b/Documentation/technical/pack-protocol.txt\n@@ -503,8 +503,8 @@ The reference discovery phase is done nearly the same way as it is in the\n fetching protocol. Each reference obj-id and name on the server is sent\n in packet-line format to the client, followed by a flush-pkt.  The only\n real difference is that the capability listing is different - the only\n-possible values are 'report-status', 'delete-refs', 'ofs-delta' and\n-'push-options'.\n+possible values are 'report-status', 'report-status-v2', 'delete-refs',\n+'ofs-delta', 'atomic' and 'push-options'.\n \n Reference Update Request and Packfile Transfer\n ----------------------------------------------\n@@ -625,7 +625,7 @@ Report Status\n -------------\n \n After receiving the pack data from the sender, the receiver sends a\n-report if 'report-status' capability is in effect.\n+report if 'report-status' or 'report-status-v2' capability is in effect.\n It is a short listing of what happened in that update.  It will first\n list the status of the packfile unpacking as either 'unpack ok' or\n 'unpack [error]'.  Then it will list the status for each of the references\n@@ -647,6 +647,43 @@ update was successful, or 'ng [refname] [error]' if the update was not.\n   error-msg         = 1*(OCTET) ; where not \"ok\"\n ----\n \n+New capability \"report-status-v2\" extends the protocol by adding new option\n+lines in order to support reporting of reference rewritten by the\n+\"proc-receive\" hook.  The \"proc-receive\" hook may handle a command for a\n+pseudo-reference which may create or update one or more references, and each\n+reference may have different name, different new-oid, and different old-oid.\n+\n+----\n+  report-status-v2  = unpack-status\n+\t\t      1*(command-status-v2)\n+\t\t      flush-pkt\n+\n+  unpack-status     = PKT-LINE(\"unpack\" SP unpack-result)\n+  unpack-result     = \"ok\" / error-msg\n+\n+  command-status-v2 = command-ok-v2 / command-fail-v2\n+  command-ok-v2     = command-ok\n+\t\t      *option-line\n+  command-fail-v2   = command-fail\n+\t\t      *option-line\n+\n+  command-ok        = PKT-LINE(\"ok\" SP refname)\n+  command-fail      = PKT-LINE(\"ng\" SP refname SP error-msg)\n+\n+  error-msg         = 1*(OCTET) ; where not \"ok\"\n+\n+  option-line       = *1(option-refname)\n+\t\t      *1(option-old-oid)\n+\t\t      *1(option-new-oid)\n+\t\t      *1(option-forced-update)\n+\n+  option-refname    = PKT-LINE(\"option\" SP \"refname\" SP refname)\n+  option-old-oid    = PKT-LINE(\"option\" SP \"old-oid\" SP obj-id)\n+  option-new-oid    = PKT-LINE(\"option\" SP \"new-oid\" SP obj-id)\n+  option-force      = PKT-LINE(\"option\" SP \"forced-update\")\n+\n+----\n+\n Updates can be unsuccessful for a number of reasons.  The reference can have\n changed since the reference discovery phase was originally sent, meaning\n someone pushed in the meantime.  The reference being pushed could be a\ndiff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt\nindex 2b267c0da6..91fd976094 100644\n--- a/Documentation/technical/protocol-capabilities.txt\n+++ b/Documentation/technical/protocol-capabilities.txt\n@@ -22,9 +22,9 @@ was sent.  Server MUST NOT ignore capabilities that client requested\n and server advertised.  As a consequence of these rules, server MUST\n NOT advertise capabilities it does not understand.\n \n-The 'atomic', 'report-status', 'delete-refs', 'quiet', and 'push-cert'\n-capabilities are sent and recognized by the receive-pack (push to server)\n-process.\n+The 'atomic', 'report-status', 'report-status-v2', 'delete-refs', 'quiet',\n+and 'push-cert' capabilities are sent and recognized by the receive-pack\n+(push to server) process.\n \n The 'ofs-delta' and 'side-band-64k' capabilities are sent and recognized\n by both upload-pack and receive-pack protocols.  The 'agent' capability\n@@ -269,6 +269,17 @@ each reference was updated successfully.  If any of those were not\n successful, it will send back an error message.  See pack-protocol.txt\n for example messages.\n \n+report-status-v2\n+----------------\n+\n+Capability 'report-status-v2' extends capability 'report-status' by\n+adding new option lines in order to support reference rewritten by\n+the\"proc-receive\" hook.  The \"proc-receive\" hook may handle a command\n+for a pseudo-reference which may create or update a reference with\n+different name, new-oid, and old-oid.  While the capability\n+\"report-status\" cannot report for such case.  See pack-protocol.txt\n+for details.\n+\n delete-refs\n -----------\n \n-- \n2.26.1.120.g98702cf3e9\n\n"},{"id":"403742","messageId":"20200815171740.6257-9-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200518094039.757-1-worldhello.net@gmail.com","subject":"[PATCH v17 08/10] t5411: test updates of remote-tracking branches","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-15T17:17:38Z","receivedAt":"2020-08-15T21:53:23Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nIn order to test update of remote-tracking branches for special refs,\nadd new \"remote.origin.fetch\" settings and test cases.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 48 +++++++++++++++++++\n 1 file changed, 48 insertions(+)\n\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex 27d58edd14..8eec3a1ed8 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -1,3 +1,18 @@\n+test_expect_success \"setup git config for remote-tracking of special refs\" '\n+\t(\n+\t\tcd workbench &&\n+\t\tif ! git config --get-all remote.origin.fetch | grep refs/for/\n+\t\tthen\n+\t\t\tgit config --add remote.origin.fetch \\\n+\t\t\t\t\"+refs/for/*:refs/t/for/*\" &&\n+\t\t\tgit config --add remote.origin.fetch \\\n+\t\t\t\t\"+refs/pull/*:refs/t/pull/*\" &&\n+\t\t\tgit config --add remote.origin.fetch \\\n+\t\t\t\t\"+refs/changes/*:refs/t/changes/*\"\n+\t\tfi\n+\t)\n+'\n+\n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL)\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n@@ -58,6 +73,17 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \ttest_cmp expect actual\n '\n \n+test_expect_success \"proc-receive: check remote-tracking #1 ($PROTOCOL)\" '\n+\tgit -C workbench show-ref |\n+\t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/t/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C workbench update-ref -d refs/t/for/master/topic\n+'\n+\n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n@@ -120,6 +146,17 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \ttest_cmp expect actual\n '\n \n+test_expect_success \"proc-receive: check remote-tracking #2 ($PROTOCOL)\" '\n+\tgit -C workbench show-ref |\n+\t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/t/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C workbench update-ref -d refs/t/for/master/topic\n+'\n+\n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n@@ -167,3 +204,14 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n \tEOF\n \ttest_cmp expect actual\n '\n+\n+test_expect_success \"proc-receive: check remote-tracking #3 ($PROTOCOL)\" '\n+\tgit -C workbench show-ref |\n+\t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/t/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C workbench update-ref -d refs/t/for/master/topic\n+'\n-- \n2.28.0\n\n"},{"id":"403744","messageId":"20200815171740.6257-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200518094039.757-1-worldhello.net@gmail.com","subject":"[PATCH v17 01/10] transport: not report a non-head push as a branch","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-15T17:17:31Z","receivedAt":"2020-08-15T21:54:08Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing a new reference (not a head or tag), report it as a new\nreference instead of a new branch.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5516-fetch-push.sh | 2 +-\n transport.c           | 9 ++++++---\n 2 files changed, 7 insertions(+), 4 deletions(-)\n\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 36ad20a849..d11382f769 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1039,7 +1039,7 @@ test_force_fetch_tag \"annotated tag\" \"-f -a -m'tag message'\"\n test_expect_success 'push --porcelain' '\n \tmk_empty testrepo &&\n \techo >.git/foo  \"To testrepo\" &&\n-\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new branch]\"  &&\n+\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new reference]\"  &&\n \techo >>.git/foo \"Done\" &&\n \tgit push >.git/bar --porcelain  testrepo refs/heads/master:refs/remotes/origin/master &&\n \t(\ndiff --git a/transport.c b/transport.c\nindex 2d4fd851dc..19b033dc6a 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -499,9 +499,12 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\t\t\t porcelain, summary_width);\n \telse if (is_null_oid(&ref->old_oid))\n \t\tprint_ref_status('*',\n-\t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n-\t\t\t\"[new branch]\"),\n-\t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t  ? \"[new tag]\"\n+\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t     ? \"[new branch]\"\n+\t\t\t\t     : \"[new reference]\")),\n+\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n-- \n2.28.0\n\n"},{"id":"403747","messageId":"20200815171740.6257-11-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200518094039.757-1-worldhello.net@gmail.com","subject":"[PATCH v17 10/10] doc: add documentation for the proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-15T17:17:40Z","receivedAt":"2020-08-15T21:55:50Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\n\"git receive-pack\" that accepts requests by \"git push\" learned to\noutsource some of the ref updates to the new \"proc-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/githooks.txt | 62 ++++++++++++++++++++++++++++++++++++++\n 1 file changed, 62 insertions(+)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 31b601e4bc..ff2f2f1fb3 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -333,6 +333,68 @@ The default 'update' hook, when enabled--and with\n `hooks.allowunannotated` config option unset or set to false--prevents\n unannotated tags to be pushed.\n \n+[[proc-receive]]\n+proc-receive\n+~~~~~~~~~~~~\n+\n+This hook is invoked by linkgit:git-receive-pack[1].  If the server has\n+set the multi-valued config variable `receive.procReceiveRefs`, and the\n+commands sent to 'receive-pack' have matching reference names, these\n+commands will be executed by this hook, instead of by the internal\n+`execute_commands()` function.  This hook is responsible for updating\n+the relevant references and reporting the results back to 'receive-pack'.\n+\n+This hook executes once for the receive operation.  It takes no\n+arguments, but uses a pkt-line format protocol to communicate with\n+'receive-pack' to read commands, push-options and send results.  In the\n+following example for the protocol, the letter 'S' stands for\n+'receive-pack' and the letter 'H' stands for this hook.\n+\n+    # Version and features negotiation.\n+    S: PKT-LINE(version=1\\0push-options atomic...)\n+    S: flush-pkt\n+    H: PKT-LINE(version=1\\0push-options...)\n+    H: flush-pkt\n+\n+    # Send commands from server to the hook.\n+    S: PKT-LINE(<old-oid> <new-oid> <ref>)\n+    S: ... ...\n+    S: flush-pkt\n+    # Send push-options only if the 'push-options' feature is enabled.\n+    S: PKT-LINE(push-option)\n+    S: ... ...\n+    S: flush-pkt\n+\n+    # Receive result from the hook.\n+    # OK, run this command successfully.\n+    H: PKT-LINE(ok <ref>)\n+    # NO, I reject it.\n+    H: PKT-LINE(ng <ref> <reason>)\n+    # Fall through, let 'receive-pack' to execute it.\n+    H: PKT-LINE(ok <ref>)\n+    H: PKT-LINE(option fall-through)\n+    # OK, but has an alternate reference.  The alternate reference name\n+    # and other status can be given in options\n+    H: PKT-LINE(ok <ref>)\n+    H: PKT-LINE(option refname <refname>)\n+    H: PKT-LINE(option old-oid <old-oid>)\n+    H: PKT-LINE(option new-oid <new-oid>)\n+    H: PKT-LINE(option forced-update)\n+    H: ... ...\n+    H: flush-pkt\n+\n+Each command for the 'proc-receive' hook may point to a pseudo-reference\n+and always has a zero-old as its old-oid, while the 'proc-receive' hook\n+may update an alternate reference and the alternate reference may exist\n+already with a non-zero old-oid.  For this case, this hook will use\n+\"option\" directives to report extended attributes for the reference given\n+by the above \"ok/ng\" directive.\n+\n+The report of the commands of this hook should have the same order as\n+the input.  The exit status of the 'proc-receive' hook only determines\n+the success or failure of the group of commands sent to it, unless\n+atomic push is in use.\n+\n [[post-receive]]\n post-receive\n ~~~~~~~~~~~~\n-- \n2.28.0\n\n"},{"id":"403750","messageId":"20200815171740.6257-7-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200518094039.757-1-worldhello.net@gmail.com","subject":"[PATCH v17 06/10] receive-pack: feed report options to post-receive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-15T17:17:36Z","receivedAt":"2020-08-15T21:57:13Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen commands are fed to the \"post-receive\" hook, report options will\nbe parsed and the real old-oid, new-oid, reference name will feed to\nthe \"post-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c                        | 29 ++++++++++++++++---\n t/t5411/once-0010-report-status-v1.sh         |  3 +-\n t/t5411/test-0032-report-with-options.sh      | 14 ++++-----\n ...est-0033-report-with-options--porcelain.sh | 14 ++++-----\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 11 +++++--\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 11 +++++--\n t/t5411/test-0038-report-mixed-refs.sh        |  2 +-\n .../test-0039-report-mixed-refs--porcelain.sh |  2 +-\n 8 files changed, 59 insertions(+), 27 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 58972aa7cf..a687218167 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -777,17 +777,38 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n {\n \tstruct receive_hook_feed_state *state = state_;\n \tstruct command *cmd = state->cmd;\n+\tstatic struct ref_push_report_options *options = NULL;\n \n \twhile (cmd &&\n \t       state->skip_broken && (cmd->report.error_message || cmd->did_not_exist))\n \t\tcmd = cmd->next;\n \tif (!cmd)\n \t\treturn -1; /* EOF */\n+\tif (!bufp)\n+\t\treturn 0; /* OK, can feed something. */\n \tstrbuf_reset(&state->buf);\n-\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n-\t\t    oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),\n-\t\t    cmd->ref_name);\n-\tstate->cmd = cmd->next;\n+\tif (!options)\n+\t\toptions = cmd->report.options;\n+\tif (options) {\n+\t\tstruct object_id *old_oid;\n+\t\tstruct object_id *new_oid;\n+\t\tconst char *ref_name;\n+\n+\t\told_oid = options->old_oid ? options->old_oid : &cmd->old_oid;\n+\t\tnew_oid = options->new_oid ? options->new_oid : &cmd->new_oid;\n+\t\tref_name = options->ref_name ? options->ref_name : cmd->ref_name;\n+\t\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n+\t\t\t    oid_to_hex(old_oid), oid_to_hex(new_oid),\n+\t\t\t    ref_name);\n+\t\toptions = options->next;\n+\t\tif (!options)\n+\t\t\tstate->cmd = cmd->next;\n+\t} else {\n+\t\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n+\t\t\t    oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),\n+\t\t\t    cmd->ref_name);\n+\t\tstate->cmd = cmd->next;\n+\t}\n \tif (bufp) {\n \t\t*bufp = state->buf.buf;\n \t\t*sizep = state->buf.len;\ndiff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh\nindex 56a86d8ed4..bf410dc418 100644\n--- a/t/t5411/once-0010-report-status-v1.sh\n+++ b/t/t5411/once-0010-report-status-v1.sh\n@@ -74,7 +74,8 @@ test_expect_success \"proc-receive: report status v1\" '\n \tpost-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n \tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n \tpost-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n-\tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2\n+\tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/changes/23/123/1\n+\tpost-receive< <COMMIT-B> <COMMIT-A> refs/for/changes/24/124/2\n \tEOF\n \ttest_cmp expect actual &&\n \ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nindex 3742ed3c3d..b77b78c49f 100644\n--- a/t/t5411/test-0032-report-with-options.sh\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -54,7 +54,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL)\" '\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n@@ -87,7 +87,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n@@ -121,7 +121,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL)\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t <OID-B>..<OID-A> HEAD -> refs/pull/123/head\n \tEOF\n@@ -153,7 +153,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL)\" '\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t <OID-B>..<OID-A> HEAD -> refs/for/master/topic\n \tEOF\n@@ -187,7 +187,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL)\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \tEOF\n@@ -237,9 +237,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n \tremote: proc-receive> option forced-update\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/pull/123/head\n \t * [new reference] HEAD -> refs/for/a/b/c/topic\ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nindex f18ba9f06f..1fe352b686 100644\n--- a/t/t5411/test-0033-report-with-options--porcelain.sh\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -55,7 +55,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL/porcelain)\"\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n@@ -90,7 +90,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n@@ -125,7 +125,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL/\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t     HEAD:refs/pull/123/head    <OID-B>..<OID-A>\n \tDone\n@@ -158,7 +158,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL/porcelain)\"\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t     HEAD:refs/for/master/topic    <OID-B>..<OID-A>\n \tDone\n@@ -193,7 +193,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL/\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \tDone\n@@ -245,9 +245,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porc\n \tremote: proc-receive> option forced-update\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/pull/123/head    [new reference]\n \t*    HEAD:refs/for/a/b/c/topic    [new reference]\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex 64662afdc6..27d58edd14 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -41,7 +41,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \t * [new reference] HEAD -> refs/changes/24/124/1\n@@ -101,7 +103,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/changes/24/124/1\n \t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n@@ -149,7 +153,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/changes/23/123/1\n \t <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2\ndiff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\nindex a7e29af0c2..77b5b22ed4 100644\n--- a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n@@ -41,7 +41,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \t*    HEAD:refs/changes/24/124/1    [new reference]\n@@ -102,7 +104,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/changes/24/124/1    [new reference]\n \t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n@@ -151,7 +155,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porc\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/changes/23/123/1    [new reference]\n \t     HEAD:refs/changes/24/124/2    <OID-A>..<OID-B>\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nindex 54bec4c026..a74a2cb449 100644\n--- a/t/t5411/test-0038-report-mixed-refs.sh\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -53,7 +53,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t <OID-A>..<OID-B> <COMMIT-B> -> master\n \t * [new branch] HEAD -> bar\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nindex 605b3cb5c9..e4baa13ea3 100644\n--- a/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -53,7 +53,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcel\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n \t*    HEAD:refs/heads/bar    [new branch]\n-- \n2.28.0\n\n"},{"id":"403755","messageId":"20200815171740.6257-4-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200518094039.757-1-worldhello.net@gmail.com","subject":"[PATCH v17 03/10] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-15T17:17:33Z","receivedAt":"2020-08-15T21:57:57Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nGit calls an internal `execute_commands` function to handle commands\nsent from client to `git-receive-pack`.  Regardless of what references\nthe user pushes, git creates or updates the corresponding references if\nthe user has write-permission.  A contributor who has no\nwrite-permission, cannot push to the repository directly.  So, the\ncontributor has to write commits to an alternate location, and sends\npull request by emails or by other ways.  We call this workflow as a\ndistributed workflow.\n\nIt would be more convenient to work in a centralized workflow like what\nGerrit provided for some cases.  For example, a read-only user who\ncannot push to a branch directly can run the following `git push`\ncommand to push commits to a pseudo reference (has a prefix \"refs/for/\",\nnot \"refs/heads/\") to create a code review.\n\n    git push origin \\\n        HEAD:refs/for/<branch-name>/<session>\n\nThe `<branch-name>` in the above example can be as simple as \"master\",\nor a more complicated branch name like \"foo/bar\".  The `<session>` in\nthe above example command can be the local branch name of the client\nside, such as \"my/topic\".\n\nWe cannot implement a centralized workflow elegantly by using\n\"pre-receive\" + \"post-receive\", because Git will call the internal\nfunction \"execute_commands\" to create references (even the special\npseudo reference) between these two hooks.  Even though we can delete\nthe temporarily created pseudo reference via the \"post-receive\" hook,\nhaving a temporary reference is not safe for concurrent pushes.\n\nSo, add a filter and a new handler to support this kind of workflow.\nThe filter will check the prefix of the reference name, and if the\ncommand has a special reference name, the filter will turn a specific\nfield (`run_proc_receive`) on for the command.  Commands with this filed\nturned on will be executed by a new handler (an hook named\n\"proc-receive\") instead of the internal `execute_commands` function.\nWe can use this \"proc-receive\" command to create pull requests or send\nemails for code review.\n\nSuggested by Junio, this \"proc-receive\" hook reads the commands,\npush-options (optional), and send result using a protocol in pkt-line\nformat.  In the following example, The letter \"S\" stands for\n\"receive-pack\" and letter \"H\" stands for the hook.\n\n    # Version and features negotiation.\n    S: PKT-LINE(version=1\\0push-options atomic...)\n    S: flush-pkt\n    H: PKT-LINE(version=1\\0push-options...)\n    H: flush-pkt\n\n    # Send commands from server to the hook.\n    S: PKT-LINE(<old-oid> <new-oid> <ref>)\n    S: ... ...\n    S: flush-pkt\n    # Send push-options only if the 'push-options' feature is enabled.\n    S: PKT-LINE(push-option)\n    S: ... ...\n    S: flush-pkt\n\n    # Receive result from the hook.\n    # OK, run this command successfully.\n    H: PKT-LINE(ok <ref>)\n    # NO, I reject it.\n    H: PKT-LINE(ng <ref> <reason>)\n    # Fall through, let 'receive-pack' to execute it.\n    H: PKT-LINE(ok <ref>)\n    H: PKT-LINE(option fall-through)\n    # OK, but has an alternate reference.  The alternate reference name\n    # and other status can be given in options\n    H: PKT-LINE(ok <ref>)\n    H: PKT-LINE(option refname <refname>)\n    H: PKT-LINE(option old-oid <old-oid>)\n    H: PKT-LINE(option new-oid <new-oid>)\n    H: PKT-LINE(option forced-update)\n    H: ... ...\n    H: flush-pkt\n\nAfter receiving a command, the hook will execute the command, and may\ncreate/update different reference.  For example, a command for a pseudo\nreference \"refs/for/master/topic\" may create/update different reference\nsuch as \"refs/pull/123/head\".  The alternate reference name and other\nstatus are given in option lines.\n\nThe list of commands returned from \"proc-receive\" will replace the\nrelevant commands that are sent from user to \"receive-pack\", and\n\"receive-pack\" will continue to run the \"execute_commands\" function and\nother routines.  Finally, the result of the execution of these commands\nwill be reported to end user.\n\nThe reporting function from \"receive-pack\" to \"send-pack\" will be\nextended in latter commit just like what the \"proc-receive\" hook reports\nto \"receive-pack\".\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Makefile                                      |   1 +\n builtin/receive-pack.c                        | 351 ++++++++++++++++--\n remote.h                                      |  14 +\n t/helper/test-proc-receive.c                  | 176 +++++++++\n t/helper/test-tool.c                          |   1 +\n t/helper/test-tool.h                          |   1 +\n t/t5411/test-0011-no-hook-error.sh            |  64 ++++\n t/t5411/test-0012-no-hook-error--porcelain.sh |  66 ++++\n t/t5411/test-0013-bad-protocol.sh             | 217 +++++++++++\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 160 ++++++++\n t/t5411/test-0020-report-ng.sh                |  67 ++++\n t/t5411/test-0021-report-ng--porcelain.sh     |  69 ++++\n t/t5411/test-0022-report-unexpect-ref.sh      |  45 +++\n ...est-0023-report-unexpect-ref--porcelain.sh |  46 +++\n t/t5411/test-0024-report-unknown-ref.sh       |  34 ++\n ...test-0025-report-unknown-ref--porcelain.sh |  35 ++\n t/t5411/test-0026-push-options.sh             |  79 ++++\n t/t5411/test-0027-push-options--porcelain.sh  |  82 ++++\n t/t5411/test-0030-report-ok.sh                |  35 ++\n t/t5411/test-0031-report-ok--porcelain.sh     |  36 ++\n t/t5411/test-0032-report-with-options.sh      | 256 +++++++++++++\n ...est-0033-report-with-options--porcelain.sh | 265 +++++++++++++\n t/t5411/test-0034-report-ft.sh                |  44 +++\n t/t5411/test-0035-report-ft--porcelain.sh     |  45 +++\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 159 ++++++++\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 162 ++++++++\n t/t5411/test-0038-report-mixed-refs.sh        |  89 +++++\n .../test-0039-report-mixed-refs--porcelain.sh |  91 +++++\n 28 files changed, 2659 insertions(+), 31 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100644 t/t5411/test-0011-no-hook-error.sh\n create mode 100644 t/t5411/test-0012-no-hook-error--porcelain.sh\n create mode 100644 t/t5411/test-0013-bad-protocol.sh\n create mode 100644 t/t5411/test-0014-bad-protocol--porcelain.sh\n create mode 100644 t/t5411/test-0020-report-ng.sh\n create mode 100644 t/t5411/test-0021-report-ng--porcelain.sh\n create mode 100644 t/t5411/test-0022-report-unexpect-ref.sh\n create mode 100644 t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n create mode 100644 t/t5411/test-0024-report-unknown-ref.sh\n create mode 100644 t/t5411/test-0025-report-unknown-ref--porcelain.sh\n create mode 100644 t/t5411/test-0026-push-options.sh\n create mode 100644 t/t5411/test-0027-push-options--porcelain.sh\n create mode 100644 t/t5411/test-0030-report-ok.sh\n create mode 100644 t/t5411/test-0031-report-ok--porcelain.sh\n create mode 100644 t/t5411/test-0032-report-with-options.sh\n create mode 100644 t/t5411/test-0033-report-with-options--porcelain.sh\n create mode 100644 t/t5411/test-0034-report-ft.sh\n create mode 100644 t/t5411/test-0035-report-ft--porcelain.sh\n create mode 100644 t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n create mode 100644 t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n create mode 100644 t/t5411/test-0038-report-mixed-refs.sh\n create mode 100644 t/t5411/test-0039-report-mixed-refs--porcelain.sh\n\ndiff --git a/Makefile b/Makefile\nindex 65f8cfb236..48c048584e 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -719,6 +719,7 @@ TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\n+TEST_BUILTINS_OBJS += test-proc-receive.o\n TEST_BUILTINS_OBJS += test-progress.o\n TEST_BUILTINS_OBJS += test-reach.o\n TEST_BUILTINS_OBJS += test-read-cache.o\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 439f29d6c7..d0b33ee84b 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -310,11 +310,14 @@ static void write_head_info(void)\n \tpacket_flush(1);\n }\n \n+#define RUN_PROC_RECEIVE_SCHEDULED\t1\n+#define RUN_PROC_RECEIVE_RETURNED\t2\n struct command {\n \tstruct command *next;\n-\tconst char *error_string;\n+\tstruct ref_push_report report;\n \tunsigned int skip_update:1,\n-\t\t     did_not_exist:1;\n+\t\t     did_not_exist:1,\n+\t\t     run_proc_receive:2;\n \tint index;\n \tstruct object_id old_oid;\n \tstruct object_id new_oid;\n@@ -775,7 +778,7 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n \tstruct command *cmd = state->cmd;\n \n \twhile (cmd &&\n-\t       state->skip_broken && (cmd->error_string || cmd->did_not_exist))\n+\t       state->skip_broken && (cmd->report.error_message || cmd->did_not_exist))\n \t\tcmd = cmd->next;\n \tif (!cmd)\n \t\treturn -1; /* EOF */\n@@ -840,6 +843,268 @@ static int run_update_hook(struct command *cmd)\n \treturn finish_command(&proc);\n }\n \n+static struct command *find_command_by_refname(const struct command *list,\n+\t\t\t\t\t       const char *refname)\n+{\n+\tfor (; list; list = list->next)\n+\t\tif (!strcmp(list->ref_name, refname))\n+\t\t\treturn (struct command *)list;\n+\treturn NULL;\n+}\n+\n+static int read_proc_receive_report(struct packet_reader *reader,\n+\t\t\t\t    struct command *commands,\n+\t\t\t\t    struct strbuf *errmsg)\n+{\n+\tstruct command *cmd;\n+\tstruct command *hint = NULL;\n+\tint code = 0;\n+\tint new_options = 1;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *head;\n+\t\tconst char *refname;\n+\t\tchar *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\thead = reader->line;\n+\t\tp = strchr(head, ' ');\n+\t\tif (!p) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported incomplete status line: '%s'\\n\", head);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\t*p++ = '\\0';\n+\t\tif (!strcmp(head, \"option\")) {\n+\t\t\tstruct ref_push_report_options *options;\n+\t\t\tconst char *key, *val;\n+\n+\t\t\tif (!hint) {\n+\t\t\t       if (new_options) {\n+\t\t\t\t\tstrbuf_addstr(errmsg, \"proc-receive reported 'option' without a matching 'ok/ng' directive\\n\");\n+\t\t\t\t\tnew_options = 0;\n+\t\t\t\t}\n+\t\t\t\tcode = -1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\toptions = hint->report.options;\n+\t\t\twhile (options && options->next)\n+\t\t\t\toptions = options->next;\n+\t\t\tif (new_options) {\n+\t\t\t\tif (!options) {\n+\t\t\t\t\thint->report.options = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\t\toptions = hint->report.options;\n+\t\t\t\t} else {\n+\t\t\t\t\toptions->next = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\t\toptions = options->next;\n+\t\t\t\t}\n+\t\t\t\tnew_options = 0;\n+\t\t\t}\n+\t\t\tassert(options);\n+\t\t\tkey = p;\n+\t\t\tp = strchr(key, ' ');\n+\t\t\tif (p)\n+\t\t\t\t*p++ = '\\0';\n+\t\t\tval = p;\n+\t\t\tif (!strcmp(key, \"refname\"))\n+\t\t\t\toptions->ref_name = xstrdup_or_null(val);\n+\t\t\telse if (!strcmp(key, \"old-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &old_oid, &val))\n+\t\t\t\toptions->old_oid = oiddup(&old_oid);\n+\t\t\telse if (!strcmp(key, \"new-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &new_oid, &val))\n+\t\t\t\toptions->new_oid = oiddup(&new_oid);\n+\t\t\telse if (!strcmp(key, \"forced-update\"))\n+\t\t\t\toptions->forced_update = 1;\n+\t\t\telse if (!strcmp(key, \"fall-through\"))\n+\t\t\t\t/* Fall through, let 'receive-pack' to execute it. */\n+\t\t\t\thint->run_proc_receive = 0;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\trefname = p;\n+\t\tp = strchr(refname, ' ');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n+\t\tif (strcmp(head, \"ok\") && strcmp(head, \"ng\")) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported bad status '%s' on ref '%s'\\n\",\n+\t\t\t\t    head, refname);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/* first try searching at our hint, falling back to all refs */\n+\t\tif (hint)\n+\t\t\thint = find_command_by_refname(hint, refname);\n+\t\tif (!hint)\n+\t\t\thint = find_command_by_refname(commands, refname);\n+\t\tif (!hint) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unknown ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!hint->run_proc_receive) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unexpected ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(head, \"ng\")) {\n+\t\t\tif (p)\n+\t\t\t\thint->report.error_message = xstrdup(p);\n+\t\t\telse\n+\t\t\t\thint->report.error_message = \"failed\";\n+\t\t\tcode = -1;\n+\t\t}\n+\t\tif (hint->run_proc_receive)\n+\t\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n+\t\tnew_options = 1;\n+\t}\n+\n+\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\tif (cmd->run_proc_receive && !cmd->report.error_message &&\n+\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {\n+\t\t    cmd->report.error_message = \"proc-receive failed to report status\";\n+\t\t    code = -1;\n+\t\t}\n+\treturn code;\n+}\n+\n+static int run_proc_receive_hook(struct command *commands,\n+\t\t\t\t const struct string_list *push_options)\n+{\n+\tstruct child_process proc = CHILD_PROCESS_INIT;\n+\tstruct async muxer;\n+\tstruct command *cmd;\n+\tconst char *argv[2];\n+\tstruct packet_reader reader;\n+\tstruct strbuf cap = STRBUF_INIT;\n+\tstruct strbuf errmsg = STRBUF_INIT;\n+\tint hook_use_push_options = 0;\n+\tint version = 0;\n+\tint code;\n+\n+\targv[0] = find_hook(\"proc-receive\");\n+\tif (!argv[0]) {\n+\t\trp_error(\"cannot find hook 'proc-receive'\");\n+\t\treturn -1;\n+\t}\n+\targv[1] = NULL;\n+\n+\tproc.argv = argv;\n+\tproc.in = -1;\n+\tproc.out = -1;\n+\tproc.trace2_hook_name = \"proc-receive\";\n+\n+\tif (use_sideband) {\n+\t\tmemset(&muxer, 0, sizeof(muxer));\n+\t\tmuxer.proc = copy_to_sideband;\n+\t\tmuxer.in = -1;\n+\t\tcode = start_async(&muxer);\n+\t\tif (code)\n+\t\t\treturn code;\n+\t\tproc.err = muxer.in;\n+\t} else {\n+\t\tproc.err = 0;\n+\t}\n+\n+\tcode = start_command(&proc);\n+\tif (code) {\n+\t\tif (use_sideband)\n+\t\t\tfinish_async(&muxer);\n+\t\treturn code;\n+\t}\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\n+\t/* Version negotiaton */\n+\tpacket_reader_init(&reader, proc.out, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_GENTLE_ON_EOF);\n+\tif (use_atomic)\n+\t\tstrbuf_addstr(&cap, \" atomic\");\n+\tif (use_push_options)\n+\t\tstrbuf_addstr(&cap, \" push-options\");\n+\tif (cap.len) {\n+\t\tpacket_write_fmt(proc.in, \"version=1%c%s\\n\", '\\0', cap.buf + 1);\n+\t\tstrbuf_release(&cap);\n+\t} else {\n+\t\tpacket_write_fmt(proc.in, \"version=1\\n\");\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader.pktlen > 8 && starts_with(reader.line, \"version=\")) {\n+\t\t\tversion = atoi(reader.line + 8);\n+\t\t\tlinelen = strlen(reader.line);\n+\t\t\tif (linelen < reader.pktlen) {\n+\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\thook_use_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (version != 1) {\n+\t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n+\t\t\t    version);\n+\t\tcode = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\t/* Send commands */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->report.error_message)\n+\t\t\tcontinue;\n+\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n+\t\t\t\t oid_to_hex(&cmd->old_oid),\n+\t\t\t\t oid_to_hex(&cmd->new_oid),\n+\t\t\t\t cmd->ref_name);\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\t/* Send push options */\n+\tif (hook_use_push_options) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tfor_each_string_list_item(item, push_options)\n+\t\t\tpacket_write_fmt(proc.in, \"%s\", item->string);\n+\t\tpacket_flush(proc.in);\n+\t}\n+\n+\t/* Read result from proc-receive */\n+\tcode = read_proc_receive_report(&reader, commands, &errmsg);\n+\n+cleanup:\n+\tclose(proc.in);\n+\tclose(proc.out);\n+\tif (use_sideband)\n+\t\tfinish_async(&muxer);\n+\tif (finish_command(&proc))\n+\t\tcode = -1;\n+\tif (errmsg.len >0) {\n+\t\tchar *p = errmsg.buf;\n+\n+\t\tp += errmsg.len - 1;\n+\t\tif (*p == '\\n')\n+\t\t\t*p = '\\0';\n+\t\trp_error(\"%s\", errmsg.buf);\n+\t\tstrbuf_release(&errmsg);\n+\t}\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn code;\n+}\n+\n static char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n@@ -1203,7 +1468,7 @@ static void run_update_post_hook(struct command *commands)\n \t\treturn;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (cmd->error_string || cmd->did_not_exist)\n+\t\tif (cmd->report.error_message || cmd->did_not_exist)\n \t\t\tcontinue;\n \t\tif (!proc.args.nr)\n \t\t\tstrvec_push(&proc.args, hook);\n@@ -1237,7 +1502,7 @@ static void check_aliased_update_internal(struct command *cmd,\n \tif (!dst_name) {\n \t\trp_error(\"refusing update to broken symref '%s'\", cmd->ref_name);\n \t\tcmd->skip_update = 1;\n-\t\tcmd->error_string = \"broken symref\";\n+\t\tcmd->report.error_message = \"broken symref\";\n \t\treturn;\n \t}\n \tdst_name = strip_namespace(dst_name);\n@@ -1264,7 +1529,7 @@ static void check_aliased_update_internal(struct command *cmd,\n \t\t find_unique_abbrev(&dst_cmd->old_oid, DEFAULT_ABBREV),\n \t\t find_unique_abbrev(&dst_cmd->new_oid, DEFAULT_ABBREV));\n \n-\tcmd->error_string = dst_cmd->error_string =\n+\tcmd->report.error_message = dst_cmd->report.error_message =\n \t\t\"inconsistent aliased update\";\n }\n \n@@ -1293,7 +1558,7 @@ static void check_aliased_updates(struct command *commands)\n \tstring_list_sort(&ref_list);\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!cmd->error_string)\n+\t\tif (!cmd->report.error_message)\n \t\t\tcheck_aliased_update(cmd, &ref_list);\n \t}\n \n@@ -1330,7 +1595,7 @@ static void set_connectivity_errors(struct command *commands,\n \t\t\t\t     &opt))\n \t\t\tcontinue;\n \n-\t\tcmd->error_string = \"missing necessary objects\";\n+\t\tcmd->report.error_message = \"missing necessary objects\";\n \t}\n }\n \n@@ -1369,7 +1634,7 @@ static void reject_updates_to_hidden(struct command *commands)\n \tprefix_len = refname_full.len;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (cmd->error_string)\n+\t\tif (cmd->report.error_message)\n \t\t\tcontinue;\n \n \t\tstrbuf_setlen(&refname_full, prefix_len);\n@@ -1378,9 +1643,9 @@ static void reject_updates_to_hidden(struct command *commands)\n \t\tif (!ref_is_hidden(cmd->ref_name, refname_full.buf))\n \t\t\tcontinue;\n \t\tif (is_null_oid(&cmd->new_oid))\n-\t\t\tcmd->error_string = \"deny deleting a hidden ref\";\n+\t\t\tcmd->report.error_message = \"deny deleting a hidden ref\";\n \t\telse\n-\t\t\tcmd->error_string = \"deny updating a hidden ref\";\n+\t\t\tcmd->report.error_message = \"deny updating a hidden ref\";\n \t}\n \n \tstrbuf_release(&refname_full);\n@@ -1388,7 +1653,7 @@ static void reject_updates_to_hidden(struct command *commands)\n \n static int should_process_cmd(struct command *cmd)\n {\n-\treturn !cmd->error_string && !cmd->skip_update;\n+\treturn !cmd->report.error_message && !cmd->skip_update;\n }\n \n static void warn_if_skipped_connectivity_check(struct command *commands,\n@@ -1415,24 +1680,24 @@ static void execute_commands_non_atomic(struct command *commands,\n \tstruct strbuf err = STRBUF_INIT;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\ttransaction = ref_transaction_begin(&err);\n \t\tif (!transaction) {\n \t\t\trp_error(\"%s\", err.buf);\n \t\t\tstrbuf_reset(&err);\n-\t\t\tcmd->error_string = \"transaction failed to start\";\n+\t\t\tcmd->report.error_message = \"transaction failed to start\";\n \t\t\tcontinue;\n \t\t}\n \n-\t\tcmd->error_string = update(cmd, si);\n+\t\tcmd->report.error_message = update(cmd, si);\n \n-\t\tif (!cmd->error_string\n+\t\tif (!cmd->report.error_message\n \t\t    && ref_transaction_commit(transaction, &err)) {\n \t\t\trp_error(\"%s\", err.buf);\n \t\t\tstrbuf_reset(&err);\n-\t\t\tcmd->error_string = \"failed to update ref\";\n+\t\t\tcmd->report.error_message = \"failed to update ref\";\n \t\t}\n \t\tref_transaction_free(transaction);\n \t}\n@@ -1455,12 +1720,12 @@ static void execute_commands_atomic(struct command *commands,\n \t}\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n-\t\tcmd->error_string = update(cmd, si);\n+\t\tcmd->report.error_message = update(cmd, si);\n \n-\t\tif (cmd->error_string)\n+\t\tif (cmd->report.error_message)\n \t\t\tgoto failure;\n \t}\n \n@@ -1473,8 +1738,8 @@ static void execute_commands_atomic(struct command *commands,\n \n failure:\n \tfor (cmd = commands; cmd; cmd = cmd->next)\n-\t\tif (!cmd->error_string)\n-\t\t\tcmd->error_string = reported_error;\n+\t\tif (!cmd->report.error_message)\n+\t\t\tcmd->report.error_message = reported_error;\n \n cleanup:\n \tref_transaction_free(transaction);\n@@ -1491,10 +1756,11 @@ static void execute_commands(struct command *commands,\n \tstruct iterate_data data;\n \tstruct async muxer;\n \tint err_fd = 0;\n+\tint run_proc_receive = 0;\n \n \tif (unpacker_error) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n-\t\t\tcmd->error_string = \"unpacker error\";\n+\t\t\tcmd->report.error_message = \"unpacker error\";\n \t\treturn;\n \t}\n \n@@ -1520,10 +1786,25 @@ static void execute_commands(struct command *commands,\n \n \treject_updates_to_hidden(commands);\n \n+\t/*\n+\t * Try to find commands that have special prefix in their reference names,\n+\t * and mark them to run an external \"proc-receive\" hook later.\n+\t */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!should_process_cmd(cmd))\n+\t\t\tcontinue;\n+\n+\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n+\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n+\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\trun_proc_receive = 1;\n+\t\t}\n+\t}\n+\n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\t\tif (!cmd->error_string)\n-\t\t\t\tcmd->error_string = \"pre-receive hook declined\";\n+\t\t\tif (!cmd->report.error_message)\n+\t\t\t\tcmd->report.error_message = \"pre-receive hook declined\";\n \t\t}\n \t\treturn;\n \t}\n@@ -1534,8 +1815,8 @@ static void execute_commands(struct command *commands,\n \t */\n \tif (tmp_objdir_migrate(tmp_objdir) < 0) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\t\tif (!cmd->error_string)\n-\t\t\t\tcmd->error_string = \"unable to migrate objects to permanent storage\";\n+\t\t\tif (!cmd->report.error_message)\n+\t\t\t\tcmd->report.error_message = \"unable to migrate objects to permanent storage\";\n \t\t}\n \t\treturn;\n \t}\n@@ -1546,6 +1827,14 @@ static void execute_commands(struct command *commands,\n \tfree(head_name_to_free);\n \thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, NULL, NULL);\n \n+\tif (run_proc_receive &&\n+\t    run_proc_receive_hook(commands, push_options))\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tif (!cmd->report.error_message &&\n+\t\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED) &&\n+\t\t\t    (cmd->run_proc_receive || use_atomic))\n+\t\t\t\tcmd->report.error_message = \"fail to run proc-receive hook\";\n+\n \tif (use_atomic)\n \t\texecute_commands_atomic(commands, si);\n \telse\n@@ -1916,7 +2205,7 @@ static void update_shallow_info(struct command *commands,\n \t\tif (is_null_oid(&cmd->new_oid))\n \t\t\tcontinue;\n \t\tif (ref_status[cmd->index]) {\n-\t\t\tcmd->error_string = \"shallow update not allowed\";\n+\t\t\tcmd->report.error_message = \"shallow update not allowed\";\n \t\t\tcmd->skip_update = 1;\n \t\t}\n \t}\n@@ -1931,12 +2220,12 @@ static void report(struct command *commands, const char *unpack_status)\n \tpacket_buf_write(&buf, \"unpack %s\\n\",\n \t\t\t unpack_status ? unpack_status : \"ok\");\n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!cmd->error_string)\n+\t\tif (!cmd->report.error_message)\n \t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n \t\t\t\t\t cmd->ref_name);\n \t\telse\n \t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n-\t\t\t\t\t cmd->ref_name, cmd->error_string);\n+\t\t\t\t\t cmd->ref_name, cmd->report.error_message);\n \t}\n \tpacket_buf_flush(&buf);\n \n@@ -2040,7 +2329,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\tif (!check_cert_push_options(&push_options)) {\n \t\t\tstruct command *cmd;\n \t\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n-\t\t\t\tcmd->error_string = \"inconsistent push options\";\n+\t\t\t\tcmd->report.error_message = \"inconsistent push options\";\n \t\t}\n \n \t\tprepare_shallow_info(&si, &shallow);\ndiff --git a/remote.h b/remote.h\nindex 5e3ea5a26d..ab2e86a2a5 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -93,6 +93,20 @@ int for_each_remote(each_remote_fn fn, void *priv);\n \n int remote_has_url(struct remote *remote, const char *url);\n \n+struct ref_push_report_options {\n+\tconst char *ref_name;\n+\tstruct object_id *old_oid;\n+\tstruct object_id *new_oid;\n+\tunsigned int forced_update:1;\n+\tstruct ref_push_report_options *next;\n+};\n+\n+struct ref_push_report {\n+\tconst char *message;\n+\tconst char *error_message;\n+\tstruct ref_push_report_options *options;\n+};\n+\n struct ref {\n \tstruct ref *next;\n \tstruct object_id old_oid;\ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nnew file mode 100644\nindex 0000000000..4f352a7baf\n--- /dev/null\n+++ b/t/helper/test-proc-receive.c\n@@ -0,0 +1,176 @@\n+#include \"cache.h\"\n+#include \"connect.h\"\n+#include \"parse-options.h\"\n+#include \"pkt-line.h\"\n+#include \"sigchain.h\"\n+#include \"test-tool.h\"\n+\n+static const char *proc_receive_usage[] = {\n+\t\"test-tool proc-receive [<options>...]\",\n+\tNULL\n+};\n+\n+static int die_version = 0;\n+static int die_readline = 0;\n+static int no_push_options = 0;\n+static int use_atomic = 0;\n+static int use_push_options = 0;\n+static int verbose = 0;\n+static int version = 1;\n+static struct string_list returns = STRING_LIST_INIT_NODUP;\n+\n+struct command {\n+\tstruct command *next;\n+\tconst char *error_string;\n+\tunsigned int skip_update:1,\n+\t\t     did_not_exist:1;\n+\tint index;\n+\tstruct object_id old_oid;\n+\tstruct object_id new_oid;\n+\tchar ref_name[FLEX_ARRAY]; /* more */\n+};\n+\n+static void proc_receive_verison(struct packet_reader *reader) {\n+\tint server_version = 0;\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader->pktlen > 8 && starts_with(reader->line, \"version=\")) {\n+\t\t\tserver_version = atoi(reader->line+8);\n+\t\t\tlinelen = strlen(reader->line);\n+\t\t\tif (linelen < reader->pktlen) {\n+\t\t\t\tconst char *feature_list = reader->line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"atomic\"))\n+\t\t\t\t\tuse_atomic= 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tuse_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (server_version != 1 || die_version)\n+\t\tdie(\"bad protocol version: %d\", server_version);\n+\n+\tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n+\t\t\t version, '\\0',\n+\t\t\t use_push_options && !no_push_options ? \"push-options\": \"\");\n+\tpacket_flush(1);\n+}\n+\n+static void proc_receive_read_commands(struct packet_reader *reader,\n+\t\t\t\t       struct command **commands)\n+{\n+\tstruct command **tail = commands;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tstruct command *cmd;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    die_readline)\n+\t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n+\t\t\t    reader->line);\n+\t\trefname = p;\n+\t\tFLEX_ALLOC_STR(cmd, ref_name, refname);\n+\t\toidcpy(&cmd->old_oid, &old_oid);\n+\t\toidcpy(&cmd->new_oid, &new_oid);\n+\n+\t\t*tail = cmd;\n+\t\ttail = &cmd->next;\n+\t}\n+}\n+\n+static void proc_receive_read_push_options(struct packet_reader *reader,\n+\t\t\t\t\t   struct string_list *options)\n+{\n+\n+\tif (no_push_options || !use_push_options)\n+\t       return;\n+\n+\twhile (1) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tstring_list_append(options, reader->line);\n+\t}\n+}\n+\n+int cmd__proc_receive(int argc, const char **argv)\n+{\n+\tint nongit_ok = 0;\n+\tstruct packet_reader reader;\n+\tstruct command *commands = NULL;\n+\tstruct string_list push_options = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"no-push-options\", &no_push_options,\n+\t\t\t \"disable push options\"),\n+\t\tOPT_BOOL(0, \"die-version\", &die_version,\n+\t\t\t \"die during version negotiation\"),\n+\t\tOPT_BOOL(0, \"die-readline\", &die_readline,\n+\t\t\t \"die when readline\"),\n+\t\tOPT_STRING_LIST('r', \"return\", &returns, \"old/new/ref/status/msg\",\n+\t\t\t\t\"return of results\"),\n+\t\tOPT__VERBOSE(&verbose, \"be verbose\"),\n+\t\tOPT_INTEGER('V', \"version\", &version,\n+\t\t\t    \"use this protocol version number\"),\n+\t\tOPT_END()\n+\t};\n+\n+\tsetup_git_directory_gently(&nongit_ok);\n+\n+\targc = parse_options(argc, argv, \"test-tools\", options, proc_receive_usage, 0);\n+\tif (argc > 0)\n+\t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n+\tpacket_reader_init(&reader, 0, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\tproc_receive_verison(&reader);\n+\tproc_receive_read_commands(&reader, &commands);\n+\tproc_receive_read_push_options(&reader, &push_options);\n+\n+\tif (verbose) {\n+\t\tstruct command *cmd;\n+\n+\t\tif (use_push_options || use_atomic)\n+\t\t\tfprintf(stderr, \"proc-receive:%s%s\\n\",\n+\t\t\t\tuse_atomic? \" atomic\": \"\",\n+\t\t\t\tuse_push_options ? \" push_options\": \"\");\n+\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tfprintf(stderr, \"proc-receive< %s %s %s\\n\",\n+\t\t\t\toid_to_hex(&cmd->old_oid),\n+\t\t\t\toid_to_hex(&cmd->new_oid),\n+\t\t\t\tcmd->ref_name);\n+\n+\t\tif (push_options.nr > 0)\n+\t\t\tfor_each_string_list_item(item, &push_options)\n+\t\t\t\tfprintf(stderr, \"proc-receive< %s\\n\", item->string);\n+\n+\t\tif (returns.nr)\n+\t\t\tfor_each_string_list_item(item, &returns)\n+\t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n+\t}\n+\n+\tif (returns.nr)\n+\t\tfor_each_string_list_item(item, &returns)\n+\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n+\tpacket_flush(1);\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 590b2efca7..a0d3966b29 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -46,6 +46,7 @@ static struct test_cmd cmds[] = {\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\n+\t{ \"proc-receive\", cmd__proc_receive},\n \t{ \"progress\", cmd__progress },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex ddc8e990e9..07034d3f38 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -35,6 +35,7 @@ int cmd__parse_pathspec_file(int argc, const char** argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\n+int cmd__proc_receive(int argc, const char **argv);\n int cmd__progress(int argc, const char **argv);\n int cmd__reach(int argc, const char **argv);\n int cmd__read_cache(int argc, const char **argv);\ndiff --git a/t/t5411/test-0011-no-hook-error.sh b/t/t5411/test-0011-no-hook-error.sh\nnew file mode 100644\nindex 0000000000..bb6ec92a92\n--- /dev/null\n+++ b/t/t5411/test-0011-no-hook-error.sh\n@@ -0,0 +1,64 @@\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\t$B:master \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] <COMMIT-B> -> master (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0012-no-hook-error--porcelain.sh b/t/t5411/test-0012-no-hook-error--porcelain.sh\nnew file mode 100644\nindex 0000000000..4814f74dc2\n--- /dev/null\n+++ b/t/t5411/test-0012-no-hook-error--porcelain.sh\n@@ -0,0 +1,66 @@\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain --atomic origin \\\n+\t\t$B:master \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t!    <COMMIT-B>:refs/heads/master    [remote rejected] (fail to run proc-receive hook)\n+\t!    HEAD:refs/heads/next    [remote rejected] (fail to run proc-receive hook)\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh\nnew file mode 100644\nindex 0000000000..c5fe4cb37b\n--- /dev/null\n+++ b/t/t5411/test-0013-bad-protocol.sh\n@@ -0,0 +1,217 @@\n+test_expect_success \"setup proc-receive hook (unknown version, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\t# Check status report for git-push\n+\tsed -n \\\n+\t\t-e \"/^To / { p; n; p; }\" \\\n+\t\t<actual >actual-report &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual-report &&\n+\n+\t# Check error message from \"receive-pack\", but ignore unstable fatal error\n+\t# message (\"remote: fatal: the remote end hung up unexpectedly\") which\n+\t# is different from the remote HTTP server with different locale settings.\n+\tgrep \"^remote: error:\" <actual >actual-error &&\n+\tcat >expect <<-EOF &&\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tEOF\n+\ttest_cmp expect actual-error &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-version, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-version\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-version, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: fatal: bad protocol version: 1\n+\tremote: error: proc-receive version \"0\" is not supported\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-readline, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-readline\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-readline, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\tgrep \"remote: fatal: protocol error: expected \\\"old new ref\\\", got \\\"<ZERO-OID> <COMMIT-A> refs/for/master/topic\\\"\" actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok\n+\tremote: error: proc-receive reported incomplete status line: \"ok\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"xx refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> xx refs/for/master/topic\n+\tremote: error: proc-receive reported bad status \"xx\" on ref \"refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0014-bad-protocol--porcelain.sh b/t/t5411/test-0014-bad-protocol--porcelain.sh\nnew file mode 100644\nindex 0000000000..53b47b0185\n--- /dev/null\n+++ b/t/t5411/test-0014-bad-protocol--porcelain.sh\n@@ -0,0 +1,160 @@\n+test_expect_success \"setup proc-receive hook (unknown version, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\t# Check status report for git-push\n+\tsed -n \\\n+\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n+\t\t<actual >actual-report &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual-report &&\n+\n+\t# Check error message from \"receive-pack\", but ignore unstable fatal error\n+\t# message (\"remote: fatal: the remote end hung up unexpectedly\") which\n+\t# is different from the remote HTTP server with different locale settings.\n+\tgrep \"^remote: error:\" <actual >actual-error &&\n+\tcat >expect <<-EOF &&\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tEOF\n+\ttest_cmp expect actual-error &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok\n+\tremote: error: proc-receive reported incomplete status line: \"ok\"\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"xx refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> xx refs/for/master/topic\n+\tremote: error: proc-receive reported bad status \"xx\" on ref \"refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0020-report-ng.sh b/t/t5411/test-0020-report-ng.sh\nnew file mode 100644\nindex 0000000000..f726b7ca9c\n--- /dev/null\n+++ b/t/t5411/test-0020-report-ng.sh\n@@ -0,0 +1,67 @@\n+test_expect_success \"setup proc-receive hook (ng, no message, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic error msg\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0021-report-ng--porcelain.sh b/t/t5411/test-0021-report-ng--porcelain.sh\nnew file mode 100644\nindex 0000000000..fbf5569103\n--- /dev/null\n+++ b/t/t5411/test-0021-report-ng--porcelain.sh\n@@ -0,0 +1,69 @@\n+test_expect_success \"setup proc-receive hook (ng, no message, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (failed)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic error msg\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (error msg)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0022-report-unexpect-ref.sh b/t/t5411/test-0022-report-unexpect-ref.sh\nnew file mode 100644\nindex 0000000000..92a415b929\n--- /dev/null\n+++ b/t/t5411/test-0022-report-unexpect-ref.sh\n@@ -0,0 +1,45 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: error: proc-receive reported status on unexpected ref: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\ndiff --git a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\nnew file mode 100644\nindex 0000000000..acbf93e40a\n--- /dev/null\n+++ b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n@@ -0,0 +1,46 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: error: proc-receive reported status on unexpected ref: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\ndiff --git a/t/t5411/test-0024-report-unknown-ref.sh b/t/t5411/test-0024-report-unknown-ref.sh\nnew file mode 100644\nindex 0000000000..c3946f329a\n--- /dev/null\n+++ b/t/t5411/test-0024-report-unknown-ref.sh\n@@ -0,0 +1,34 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: report unknown reference ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0025-report-unknown-ref--porcelain.sh b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\nnew file mode 100644\nindex 0000000000..d093b1a579\n--- /dev/null\n+++ b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\n@@ -0,0 +1,35 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: report unknown reference ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/a/b/c/my/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh\nnew file mode 100644\nindex 0000000000..d0c4da8b23\n--- /dev/null\n+++ b/t/t5411/test-0026-push-options.sh\n@@ -0,0 +1,79 @@\n+test_expect_success \"setup proc-receive hook and disable push-options ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions false &&\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"proc-receive: not support push options ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n+\t\tactual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"proc-receive: push with options ($PROTOCOL)\" '\n+\tgit -C workbench push \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\ndiff --git a/t/t5411/test-0027-push-options--porcelain.sh b/t/t5411/test-0027-push-options--porcelain.sh\nnew file mode 100644\nindex 0000000000..c89a1e7c57\n--- /dev/null\n+++ b/t/t5411/test-0027-push-options--porcelain.sh\n@@ -0,0 +1,82 @@\n+test_expect_success \"setup proc-receive hook and disable push-options ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions false &&\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"proc-receive: not support push options ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t--porcelain \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n+\t\tactual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"proc-receive: push with options ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push \\\n+\t\t--porcelain \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\ndiff --git a/t/t5411/test-0030-report-ok.sh b/t/t5411/test-0030-report-ok.sh\nnew file mode 100644\nindex 0000000000..44c99d3831\n--- /dev/null\n+++ b/t/t5411/test-0030-report-ok.sh\n@@ -0,0 +1,35 @@\n+test_expect_success \"setup proc-receive hook (ok, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0031-report-ok--porcelain.sh b/t/t5411/test-0031-report-ok--porcelain.sh\nnew file mode 100644\nindex 0000000000..3223b26184\n--- /dev/null\n+++ b/t/t5411/test-0031-report-ok--porcelain.sh\n@@ -0,0 +1,36 @@\n+test_expect_success \"setup proc-receive hook (ok, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nnew file mode 100644\nindex 0000000000..a743aa8018\n--- /dev/null\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -0,0 +1,256 @@\n+test_expect_success \"setup proc-receive hook (option without matching ok, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option without matching ok ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: error: proc-receive reported \"option\" without a matching \"ok/ng\" directive\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and forced-update, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and forced-update ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and old-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid and new-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (report with multiple rewrites, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/a/b/c/topic\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option forced-update\" \\\n+\t\t-r \"option new-oid $A\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/for/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/a/b/c/topic\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option forced-update\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/for/a/b/c/topic\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nnew file mode 100644\nindex 0000000000..439b97b06e\n--- /dev/null\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -0,0 +1,265 @@\n+test_expect_success \"setup proc-receive hook (option without matching ok, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option without matching ok ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: error: proc-receive reported \"option\" without a matching \"ok/ng\" directive\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and forced-update, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and forced-update ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and old-oid, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid and new-oid, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (report with multiple rewrites, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/a/b/c/topic\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option forced-update\" \\\n+\t\t-r \"option new-oid $A\"\n+\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/for/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/a/b/c/topic\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option forced-update\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/next/topic    [new reference]\n+\t*    HEAD:refs/for/a/b/c/topic    [new reference]\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0034-report-ft.sh b/t/t5411/test-0034-report-ft.sh\nnew file mode 100644\nindex 0000000000..aca2b0676c\n--- /dev/null\n+++ b/t/t5411/test-0034-report-ft.sh\n@@ -0,0 +1,44 @@\n+test_expect_success \"setup proc-receive hook (ft, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option fall-through\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(B)\n+test_expect_success \"proc-receive: fall throught, let receive-pack to execute ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option fall-through\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] <COMMIT-B> -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/for/master/topic\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             refs/for/master/topic(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/for/master/topic\n+'\ndiff --git a/t/t5411/test-0035-report-ft--porcelain.sh b/t/t5411/test-0035-report-ft--porcelain.sh\nnew file mode 100644\nindex 0000000000..30ffffb352\n--- /dev/null\n+++ b/t/t5411/test-0035-report-ft--porcelain.sh\n@@ -0,0 +1,45 @@\n+test_expect_success \"setup proc-receive hook (fall-through, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option fall-through\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(B)\n+test_expect_success \"proc-receive: fall throught, let receive-pack to execute ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\t$B:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option fall-through\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    <COMMIT-B>:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/for/master/topic\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             refs/for/master/topic(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/for/master/topic\n+'\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nnew file mode 100644\nindex 0000000000..12acf9ea1f\n--- /dev/null\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -0,0 +1,159 @@\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/23/123/1\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/2\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/23/123/1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/2\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\nnew file mode 100644\nindex 0000000000..f4a2c56e14\n--- /dev/null\n+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n@@ -0,0 +1,162 @@\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/23/123/1\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/2\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/23/123/1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/2\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nnew file mode 100644\nindex 0000000000..1dd6a97a97\n--- /dev/null\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -0,0 +1,89 @@\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/next/topic2\" \\\n+\t\t-r \"ng refs/for/next/topic1 fail to call Web API\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic2 \\\n+\t\tHEAD:refs/for/next/topic1 \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic3 \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n+\tremote: proc-receive> ok refs/for/next/topic2\n+\tremote: proc-receive> ng refs/for/next/topic1 fail to call Web API\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t * [new branch] HEAD -> bar\n+\t * [new branch] HEAD -> baz\n+\t * [new reference] HEAD -> refs/for/next/topic2\n+\t * [new branch] HEAD -> foo\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\t ! [remote rejected] HEAD -> refs/for/next/topic1 (fail to call Web API)\n+\t ! [remote rejected] HEAD -> refs/for/next/topic3 (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit update-ref -d refs/heads/foo &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+'\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nnew file mode 100644\nindex 0000000000..32ebf63dcf\n--- /dev/null\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -0,0 +1,91 @@\n+test_expect_success \"setup proc-receive hook ($PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/next/topic2\" \\\n+\t\t-r \"ng refs/for/next/topic1 fail to call Web API\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic2 \\\n+\t\tHEAD:refs/for/next/topic1 \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic3 \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n+\tremote: proc-receive> ok refs/for/next/topic2\n+\tremote: proc-receive> ng refs/for/next/topic1 fail to call Web API\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n+\t*    HEAD:refs/heads/bar    [new branch]\n+\t*    HEAD:refs/heads/baz    [new branch]\n+\t*    HEAD:refs/for/next/topic2    [new reference]\n+\t*    HEAD:refs/heads/foo    [new branch]\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\t!    HEAD:refs/for/next/topic1    [remote rejected] (fail to call Web API)\n+\t!    HEAD:refs/for/next/topic3    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit update-ref -d refs/heads/foo &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+\n+'\n-- \n2.28.0\n\n"},{"id":"403765","messageId":"20200815171740.6257-8-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200518094039.757-1-worldhello.net@gmail.com","subject":"[PATCH v17 07/10] receive-pack: new config receive.procReceiveRefs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-15T17:17:37Z","receivedAt":"2020-08-15T22:02:20Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd a new multi-valued config variable \"receive.procReceiveRefs\"\nfor `receive-pack` command, like the follows:\n\n    git config --system --add receive.procReceiveRefs refs/for\n    git config --system --add receive.procReceiveRefs refs/drafts\n\nIf the specific prefix strings given by the config variables match the\nreference names of the commands which are sent from git client to\n`receive-pack`, these commands will be executed by an external hook\n(named \"proc-receive\"), instead of the internal `execute_commands`\nfunction.\n\nFor example, if it is set to \"refs/for\", pushing to a reference such as\n\"refs/for/master\" will not create or update reference \"refs/for/master\",\nbut may create or update a pull request directly by running the hook\n\"proc-receive\".\n\nOptional modifiers can be provided in the beginning of value of the\nconfig variable to filter commands for specific actions: create (a),\nmofify (m), delete (d). E.g.:\n\n    git config --system --add receive.procReceiveRefs ad:refs/heads\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/config/receive.txt              |  19 +++\n builtin/receive-pack.c                        | 110 ++++++++++++++-\n t/t5411/once-0010-report-status-v1.sh         |   4 +\n t/t5411/test-0010-proc-receive-settings.sh    |   7 +\n t/t5411/test-0040-process-all-refs.sh         | 113 ++++++++++++++++\n .../test-0041-process-all-refs--porcelain.sh  | 114 ++++++++++++++++\n t/t5411/test-0050-proc-receive-refs.sh        | 128 ++++++++++++++++++\n 7 files changed, 488 insertions(+), 7 deletions(-)\n create mode 100644 t/t5411/test-0010-proc-receive-settings.sh\n create mode 100644 t/t5411/test-0040-process-all-refs.sh\n create mode 100644 t/t5411/test-0041-process-all-refs--porcelain.sh\n create mode 100644 t/t5411/test-0050-proc-receive-refs.sh\n\ndiff --git a/Documentation/config/receive.txt b/Documentation/config/receive.txt\nindex 65f78aac37..7f1a1bd00c 100644\n--- a/Documentation/config/receive.txt\n+++ b/Documentation/config/receive.txt\n@@ -114,6 +114,25 @@ receive.hideRefs::\n \tAn attempt to update or delete a hidden ref by `git push` is\n \trejected.\n \n+receive.procReceiveRefs::\n+\tThis is a multi-valued variable that defines reference prefixes\n+\tto match the commands in `receive-pack`.  Commands matching the\n+\tprefixes will be executed by an external hook \"proc-receive\",\n+\tinstead of the internal `execute_commands` function.  If this\n+\tvariable is not defined, the \"proc-receive\" hook will never be\n+\tused, and all commands will be executed by the internal\n+\t`execute_commands` function.\n++\n+For example, if this variable is set to \"refs/for\", pushing to reference\n+such as \"refs/for/master\" will not create or update a reference named\n+\"refs/for/master\", but may create or update a pull request directly by\n+running the hook \"proc-receive\".\n++\n+Optional modifiers can be provided in the beginning of the value to filter\n+commands for specific actions: create (a), mofify (m), delete (d). E.g.:\n++\n+\tgit config --system --add receive.procReceiveRefs ad:refs/heads\n+ \n receive.updateServerInfo::\n \tIf set to true, git-receive-pack will run git-update-server-info\n \tafter receiving data from git-push and updating refs.\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex a687218167..b7e4ee1483 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -98,6 +98,16 @@ static int keepalive_in_sec = 5;\n \n static struct tmp_objdir *tmp_objdir;\n \n+static struct proc_receive_ref {\n+\tunsigned int want_add:1,\n+\t\t     want_delete:1,\n+\t\t     want_modify:1;\n+\tchar *ref_prefix;\n+\tstruct proc_receive_ref *next;\n+} *proc_receive_ref = NULL;\n+\n+static void proc_receive_ref_append(const char *prefix);\n+\n static enum deny_action parse_deny_action(const char *var, const char *value)\n {\n \tif (value) {\n@@ -230,6 +240,13 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (strcmp(var, \"receive.procreceiverefs\") == 0) {\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tproc_receive_ref_append(value);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -325,6 +342,84 @@ struct command {\n \tchar ref_name[FLEX_ARRAY]; /* more */\n };\n \n+static void proc_receive_ref_append(const char *prefix)\n+{\n+\tstruct proc_receive_ref *ref_pattern;\n+\tchar *p;\n+\tint len;\n+\n+\tref_pattern = xcalloc(1, sizeof(struct proc_receive_ref));\n+\tp = strchr(prefix, ':');\n+\tif (p) {\n+\t\twhile (prefix < p) {\n+\t\t\tif (*prefix == 'a')\n+\t\t\t\tref_pattern->want_add = 1;\n+\t\t\telse if (*prefix == 'd')\n+\t\t\t\tref_pattern->want_delete = 1;\n+\t\t\telse if (*prefix == 'm')\n+\t\t\t\tref_pattern->want_modify = 1;\n+\t\t\tprefix++;\n+\t\t}\n+\t\tprefix++;\n+\t} else {\n+\t\tref_pattern->want_add = 1;\n+\t\tref_pattern->want_delete = 1;\n+\t\tref_pattern->want_modify = 1;\n+\t}\n+\tref_pattern->next = NULL;\n+\tref_pattern->ref_prefix = xstrdup(prefix);\n+\tlen = strlen(ref_pattern->ref_prefix);\n+\twhile (len && ref_pattern->ref_prefix[len - 1] == '/')\n+\t\tref_pattern->ref_prefix[--len] = '\\0';\n+\tif (proc_receive_ref == NULL) {\n+\t\tproc_receive_ref = ref_pattern;\n+\t} else {\n+\t\tstruct proc_receive_ref *end;\n+\n+\t\tend = proc_receive_ref;\n+\t\twhile (end->next)\n+\t\t\tend = end->next;\n+\t\tend->next = ref_pattern;\n+\t}\n+}\n+\n+static int proc_receive_ref_matches(struct command *cmd)\n+{\n+\tstruct proc_receive_ref *p;\n+\n+\tif (!proc_receive_ref)\n+\t\treturn 0;\n+\n+\tfor (p = proc_receive_ref; p; p = p->next) {\n+\t\tconst char *match = p->ref_prefix;\n+\t\tint neg = 0;\n+\t\tconst char *remains;\n+\n+\t\tif (!p->want_add && is_null_oid(&cmd->old_oid))\n+\t\t\tcontinue;\n+\t\telse if (!p->want_delete && is_null_oid(&cmd->new_oid))\n+\t\t\tcontinue;\n+\t\telse if (!p->want_modify &&\n+\t\t\t !is_null_oid(&cmd->old_oid) &&\n+\t\t\t !is_null_oid(&cmd->new_oid))\n+\t\t\tcontinue;\n+\n+\t\tif (*match == '!') {\n+\t\t\tneg = 1;\n+\t\t\tmatch++;\n+\t\t}\n+\n+\t\tif (skip_prefix(cmd->ref_name, match, &remains) &&\n+\t\t    (!*remains || *remains == '/')) {\n+\t\t\tif (!neg)\n+\t\t\t\treturn 1;\n+\t\t} else if (neg) {\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2)));\n static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2)));\n \n@@ -1812,14 +1907,15 @@ static void execute_commands(struct command *commands,\n \t * Try to find commands that have special prefix in their reference names,\n \t * and mark them to run an external \"proc-receive\" hook later.\n \t */\n-\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n-\t\t\tcontinue;\n+\tif (proc_receive_ref) {\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tif (!should_process_cmd(cmd))\n+\t\t\t\tcontinue;\n \n-\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n-\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n-\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n-\t\t\trun_proc_receive = 1;\n+\t\t\tif (proc_receive_ref_matches(cmd)) {\n+\t\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\t\trun_proc_receive = 1;\n+\t\t\t}\n \t\t}\n \t}\n \ndiff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh\nindex bf410dc418..dc2cf4a522 100644\n--- a/t/t5411/once-0010-report-status-v1.sh\n+++ b/t/t5411/once-0010-report-status-v1.sh\n@@ -1,3 +1,7 @@\n+test_expect_success \"setup receive.procReceiveRefs\" '\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs/for\n+'\n+\n test_expect_success \"setup proc-receive hook\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\ndiff --git a/t/t5411/test-0010-proc-receive-settings.sh b/t/t5411/test-0010-proc-receive-settings.sh\nnew file mode 100644\nindex 0000000000..a36809927b\n--- /dev/null\n+++ b/t/t5411/test-0010-proc-receive-settings.sh\n@@ -0,0 +1,7 @@\n+test_expect_success \"add two receive.procReceiveRefs settings\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit config --add receive.procReceiveRefs refs/for &&\n+\t\tgit config --add receive.procReceiveRefs refs/review/\n+\t)\n+'\ndiff --git a/t/t5411/test-0040-process-all-refs.sh b/t/t5411/test-0040-process-all-refs.sh\nnew file mode 100644\nindex 0000000000..b07c999f53\n--- /dev/null\n+++ b/t/t5411/test-0040-process-all-refs.sh\n@@ -0,0 +1,113 @@\n+test_expect_success \"config receive.procReceiveRefs = refs ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config --unset-all receive.procReceiveRefs &&\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup upstream branches ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $B &&\n+\t\tgit update-ref refs/heads/foo $A &&\n+\t\tgit update-ref refs/heads/bar $A &&\n+\t\tgit update-ref refs/heads/baz $A\n+\t)\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/foo\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/bar\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"proc-receive: process all refs ($PROTOCOL)\" '\n+\tgit -C workbench push -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/foo\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/bar\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> bar\n+\t - [deleted] foo\n+\t + <OID-B>...<OID-A> HEAD -> master (forced update)\n+\t <OID-A>..<OID-B> HEAD -> refs/pull/123/head\n+\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             bar(A)  baz(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+'\ndiff --git a/t/t5411/test-0041-process-all-refs--porcelain.sh b/t/t5411/test-0041-process-all-refs--porcelain.sh\nnew file mode 100644\nindex 0000000000..0dd9824616\n--- /dev/null\n+++ b/t/t5411/test-0041-process-all-refs--porcelain.sh\n@@ -0,0 +1,114 @@\n+test_expect_success \"config receive.procReceiveRefs = refs ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" config --unset-all receive.procReceiveRefs &&\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup upstream branches ($PROTOCOL/porcelain)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $B &&\n+\t\tgit update-ref refs/heads/foo $A &&\n+\t\tgit update-ref refs/heads/bar $A &&\n+\t\tgit update-ref refs/heads/baz $A\n+\t)\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook ($PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/foo\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/bar\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"proc-receive: process all refs ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/foo\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/bar\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/bar    <OID-A>..<OID-B>\n+\t-    :refs/heads/foo    [deleted]\n+\t+    HEAD:refs/heads/master    <OID-B>...<OID-A> (forced update)\n+\t     HEAD:refs/pull/123/head    <OID-A>..<OID-B>\n+\t+    HEAD:refs/pull/124/head    <OID-B>...<OID-A> (forced update)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             bar(A)  baz(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+'\ndiff --git a/t/t5411/test-0050-proc-receive-refs.sh b/t/t5411/test-0050-proc-receive-refs.sh\nnew file mode 100644\nindex 0000000000..50d4f159f8\n--- /dev/null\n+++ b/t/t5411/test-0050-proc-receive-refs.sh\n@@ -0,0 +1,128 @@\n+test_expect_success \"config receive.procReceiveRefs with modifiers ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit config --unset-all receive.procReceiveRefs &&\n+\t\tgit config --add receive.procReceiveRefs m:refs/heads/master &&\n+\t\tgit config --add receive.procReceiveRefs ad:refs/heads &&\n+\t\tgit config --add receive.procReceiveRefs \"a:!refs/heads\"\n+\t)\n+'\n+\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/tags/v123 \" \\\n+\t\t-r \"option refname refs/pull/124/head\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : master(B)  tags/v123\n+test_expect_success \"proc-receive: update branch and new tag ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tv123 >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/tags/v123\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/pull/124/head\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> refs/pull/123/head\n+\t * [new reference] v123 -> refs/pull/124/head\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup upstream: create tags/v123 ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref refs/tags/v123 $TAG &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $ZERO_OID\" \\\n+\t\t-r \"ok refs/heads/next\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option new-oid $A\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)  tags/v123\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : NULL       NULL       next(A)\n+test_expect_success \"proc-receive: create/delete branch, and delete tag ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t:refs/heads/master \\\n+\t\t$A:refs/heads/next \\\n+\t\t:refs/tags/v123 >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/master\n+\tremote: pre-receive< <TAG-v123> <ZERO-OID> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <ZERO-OID>\n+\tremote: proc-receive> ok refs/heads/next\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/pull/123/head\n+\tremote: post-receive< <TAG-v123> <ZERO-OID> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/124/head\n+\tTo <URL/of/upstream.git>\n+\t - [deleted] refs/pull/123/head\n+\t - [deleted] v123\n+\t * [new reference] <COMMIT-A> -> refs/pull/124/head\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n-- \n2.28.0\n\n"},{"id":"403767","messageId":"20200815171740.6257-6-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200518094039.757-1-worldhello.net@gmail.com","subject":"[PATCH v17 05/10] doc: add document for capability report-status-v2","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-15T17:17:35Z","receivedAt":"2020-08-15T22:02:44Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd ABNF notation for capability 'report-status-v2' which extends\ncapability 'report-status' by adding additional option lines.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/technical/pack-protocol.txt     | 43 +++++++++++++++++--\n .../technical/protocol-capabilities.txt       | 17 ++++++--\n 2 files changed, 54 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt\nindex a4573d12ce..01eb1629a8 100644\n--- a/Documentation/technical/pack-protocol.txt\n+++ b/Documentation/technical/pack-protocol.txt\n@@ -503,8 +503,8 @@ The reference discovery phase is done nearly the same way as it is in the\n fetching protocol. Each reference obj-id and name on the server is sent\n in packet-line format to the client, followed by a flush-pkt.  The only\n real difference is that the capability listing is different - the only\n-possible values are 'report-status', 'delete-refs', 'ofs-delta' and\n-'push-options'.\n+possible values are 'report-status', 'report-status-v2', 'delete-refs',\n+'ofs-delta', 'atomic' and 'push-options'.\n \n Reference Update Request and Packfile Transfer\n ----------------------------------------------\n@@ -625,7 +625,7 @@ Report Status\n -------------\n \n After receiving the pack data from the sender, the receiver sends a\n-report if 'report-status' capability is in effect.\n+report if 'report-status' or 'report-status-v2' capability is in effect.\n It is a short listing of what happened in that update.  It will first\n list the status of the packfile unpacking as either 'unpack ok' or\n 'unpack [error]'.  Then it will list the status for each of the references\n@@ -647,6 +647,43 @@ update was successful, or 'ng [refname] [error]' if the update was not.\n   error-msg         = 1*(OCTET) ; where not \"ok\"\n ----\n \n+New capability \"report-status-v2\" extends the protocol by adding new option\n+lines in order to support reporting of reference rewritten by the\n+\"proc-receive\" hook.  The \"proc-receive\" hook may handle a command for a\n+pseudo-reference which may create or update one or more references, and each\n+reference may have different name, different new-oid, and different old-oid.\n+\n+----\n+  report-status-v2  = unpack-status\n+\t\t      1*(command-status-v2)\n+\t\t      flush-pkt\n+\n+  unpack-status     = PKT-LINE(\"unpack\" SP unpack-result)\n+  unpack-result     = \"ok\" / error-msg\n+\n+  command-status-v2 = command-ok-v2 / command-fail-v2\n+  command-ok-v2     = command-ok\n+\t\t      *option-line\n+  command-fail-v2   = command-fail\n+\t\t      *option-line\n+\n+  command-ok        = PKT-LINE(\"ok\" SP refname)\n+  command-fail      = PKT-LINE(\"ng\" SP refname SP error-msg)\n+\n+  error-msg         = 1*(OCTET) ; where not \"ok\"\n+\n+  option-line       = *1(option-refname)\n+\t\t      *1(option-old-oid)\n+\t\t      *1(option-new-oid)\n+\t\t      *1(option-forced-update)\n+\n+  option-refname    = PKT-LINE(\"option\" SP \"refname\" SP refname)\n+  option-old-oid    = PKT-LINE(\"option\" SP \"old-oid\" SP obj-id)\n+  option-new-oid    = PKT-LINE(\"option\" SP \"new-oid\" SP obj-id)\n+  option-force      = PKT-LINE(\"option\" SP \"forced-update\")\n+\n+----\n+\n Updates can be unsuccessful for a number of reasons.  The reference can have\n changed since the reference discovery phase was originally sent, meaning\n someone pushed in the meantime.  The reference being pushed could be a\ndiff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt\nindex 36ccd14f97..78c0c4b3e2 100644\n--- a/Documentation/technical/protocol-capabilities.txt\n+++ b/Documentation/technical/protocol-capabilities.txt\n@@ -22,9 +22,9 @@ was sent.  Server MUST NOT ignore capabilities that client requested\n and server advertised.  As a consequence of these rules, server MUST\n NOT advertise capabilities it does not understand.\n \n-The 'atomic', 'report-status', 'delete-refs', 'quiet', and 'push-cert'\n-capabilities are sent and recognized by the receive-pack (push to server)\n-process.\n+The 'atomic', 'report-status', 'report-status-v2', 'delete-refs', 'quiet',\n+and 'push-cert' capabilities are sent and recognized by the receive-pack\n+(push to server) process.\n \n The 'ofs-delta' and 'side-band-64k' capabilities are sent and recognized\n by both upload-pack and receive-pack protocols.  The 'agent' capability\n@@ -284,6 +284,17 @@ each reference was updated successfully.  If any of those were not\n successful, it will send back an error message.  See pack-protocol.txt\n for example messages.\n \n+report-status-v2\n+----------------\n+\n+Capability 'report-status-v2' extends capability 'report-status' by\n+adding new option lines in order to support reference rewritten by\n+the\"proc-receive\" hook.  The \"proc-receive\" hook may handle a command\n+for a pseudo-reference which may create or update a reference with\n+different name, new-oid, and old-oid.  While the capability\n+\"report-status\" cannot report for such case.  See pack-protocol.txt\n+for details.\n+\n delete-refs\n -----------\n \n-- \n2.28.0\n\n"},{"id":"403768","messageId":"20200815171740.6257-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200518094039.757-1-worldhello.net@gmail.com","subject":"[PATCH v17 02/10] t5411: add basic test cases for proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-15T17:17:32Z","receivedAt":"2020-08-15T22:02:48Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nTopic \"proc-receive-hook\" will change the workflow and output of\ngit-push. Add some basic test cases in t5411 before introducing the new\ntopic.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411-proc-receive-hook.sh                  | 108 +++++++++++++\n t/t5411/common-functions.sh                   |  56 +++++++\n t/t5411/test-0000-standard-git-push.sh        | 143 +++++++++++++++++\n .../test-0001-standard-git-push--porcelain.sh | 147 ++++++++++++++++++\n t/t5411/test-0002-pre-receive-declined.sh     |  33 ++++\n ...st-0003-pre-receive-declined--porcelain.sh |  34 ++++\n 6 files changed, 521 insertions(+)\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/test-0000-standard-git-push.sh\n create mode 100644 t/t5411/test-0001-standard-git-push--porcelain.sh\n create mode 100644 t/t5411/test-0002-pre-receive-declined.sh\n create mode 100644 t/t5411/test-0003-pre-receive-declined--porcelain.sh\n\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nnew file mode 100755\nindex 0000000000..3a684353a8\n--- /dev/null\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -0,0 +1,108 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook'\n+\n+. ./test-lib.sh\n+\n+. \"$TEST_DIRECTORY\"/t5411/common-functions.sh\n+\n+setup_upstream_and_workbench () {\n+\t# Refs of upstream : master(A)\n+\t# Refs of workbench: master(A)  tags/v123\n+\ttest_expect_success \"setup upstream and workbench\" '\n+\t\trm -rf upstream.git &&\n+\t\trm -rf workbench &&\n+\t\tgit init --bare upstream.git &&\n+\t\tgit init workbench &&\n+\t\tcreate_commits_in workbench A B &&\n+\t\t(\n+\t\t\tcd workbench &&\n+\t\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\t\tgit config core.abbrev 7 &&\n+\t\t\tgit tag -m \"v123\" v123 $A &&\n+\t\t\tgit remote add origin ../upstream.git &&\n+\t\t\tgit push origin master &&\n+\t\t\tgit update-ref refs/heads/master $A $B &&\n+\t\t\tgit -C ../upstream.git update-ref \\\n+\t\t\t\trefs/heads/master $A $B\n+\t\t) &&\n+\t\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t\t# setup pre-receive hook\n+\t\twrite_script upstream.git/hooks/pre-receive <<-\\EOF &&\n+\t\texec >&2\n+\t\techo \"# pre-receive hook\"\n+\t\twhile read old new ref\n+\t\tdo\n+\t\t\techo \"pre-receive< $old $new $ref\"\n+\t\tdone\n+\t\tEOF\n+\n+\t\t# setup post-receive hook\n+\t\twrite_script upstream.git/hooks/post-receive <<-\\EOF &&\n+\t\texec >&2\n+\t\techo \"# post-receive hook\"\n+\t\twhile read old new ref\n+\t\tdo\n+\t\t\techo \"post-receive< $old $new $ref\"\n+\t\tdone\n+\t\tEOF\n+\n+\t\tupstream=upstream.git\n+\t'\n+}\n+\n+run_proc_receive_hook_test() {\n+\tcase $1 in\n+\thttp)\n+\t\tPROTOCOL=\"HTTP protocol\"\n+\t\tURL_PREFIX=\"http://.*\"\n+\t\t;;\n+\tlocal)\n+\t\tPROTOCOL=\"builtin protocol\"\n+\t\tURL_PREFIX=\"\\.\\.\"\n+\t\t;;\n+\tesac\n+\n+\t# Include test cases for both file and HTTP protocol\n+\tfor t in  \"$TEST_DIRECTORY\"/t5411/test-*.sh\n+\tdo\n+\t\t. \"$t\"\n+\tdone\n+}\n+\n+# Initialize the upstream repository and local workbench.\n+setup_upstream_and_workbench\n+\n+# Run test cases for 'proc-receive' hook on local file protocol.\n+run_proc_receive_hook_test local\n+\n+ROOT_PATH=\"$PWD\"\n+. \"$TEST_DIRECTORY\"/lib-gpg.sh\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+. \"$TEST_DIRECTORY\"/lib-terminal.sh\n+start_httpd\n+\n+# Re-initialize the upstream repository and local workbench.\n+setup_upstream_and_workbench\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup for HTTP protocol\" '\n+\tgit -C upstream.git config http.receivepack true &&\n+\tupstream=\"$HTTPD_DOCUMENT_ROOT_PATH/upstream.git\" &&\n+\tmv upstream.git \"$upstream\" &&\n+\tgit -C workbench remote set-url origin \"$HTTPD_URL/auth-push/smart/upstream.git\" &&\n+\tset_askpass user@host pass@host\n+'\n+\n+setup_askpass_helper\n+\n+# Run test cases for 'proc-receive' hook on HTTP protocol.\n+run_proc_receive_hook_test http\n+\n+test_done\ndiff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\nnew file mode 100644\nindex 0000000000..6580bebd8e\n--- /dev/null\n+++ b/t/t5411/common-functions.sh\n@@ -0,0 +1,56 @@\n+# Create commits in <repo> and assign each commit's oid to shell variables\n+# given in the arguments (A, B, and C). E.g.:\n+#\n+#     create_commits_in <repo> A B C\n+#\n+# NOTE: Never calling this function from a subshell since variable\n+# assignments will disappear when subshell exits.\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{} --)\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  We can easily prepare the expect text\n+# without having to worry about future changes of the commit ID and spaces\n+# of the output.  Single quotes are replaced with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect text.  We also\n+# remove some locale error messages, which break test if we turn on\n+# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n+# on plumbing commands.\n+make_user_friendly_and_stable_output () {\n+\tsed \\\n+\t\t-e \"s/  *\\$//\" \\\n+\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/'/\\\"/g\" \\\n+\t\t-e \"s/\t/    /g\" \\\n+\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n+\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n+\t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n+\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n+\t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n+\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\" \\\n+\t\t-e \"s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#\" \\\n+\t\t-e \"/^error: / d\"\n+}\ndiff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh\nnew file mode 100644\nindex 0000000000..e206587348\n--- /dev/null\n+++ b/t/t5411/test-0000-standard-git-push.sh\n@@ -0,0 +1,143 @@\n+# Refs of upstream : master(A)  \n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t * [new branch] HEAD -> next\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push --atomic: master(A)  next(B)\n+test_expect_success \"git-push --atomic ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\tmaster \\\n+\t\t$B:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out |\n+\t\tsed -n \\\n+\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n+\t\t\t-e \"/^ ! / { s/   */ /g; p; }\" \\\n+\t\t\t>actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [rejected] master -> master (non-fast-forward)\n+\t ! [rejected] <COMMIT-B> -> next (atomic push failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push         : master(A)  next(B)\n+test_expect_success \"non-fast-forward git-push ($PROTOCOL)\" '\n+\ttest_must_fail git \\\n+\t\t-C workbench \\\n+\t\t-c advice.pushUpdateRejected=false \\\n+\t\tpush origin \\\n+\t\tmaster \\\n+\t\t$B:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> next\n+\t ! [rejected] master -> master (non-fast-forward)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-B> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(B)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"git-push -f ($PROTOCOL)\" '\n+\tgit -C workbench push -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tmaster \\\n+\t\tmaster:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-B> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-B> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo <URL/of/upstream.git>\n+\t + <OID-B>...<OID-A> master -> master (forced update)\n+\t - [deleted] next\n+\t * [new tag] v123 -> v123\n+\t * [new reference] master -> refs/review/master/topic\n+\t * [new branch] HEAD -> a/b/c\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\ndiff --git a/t/t5411/test-0001-standard-git-push--porcelain.sh b/t/t5411/test-0001-standard-git-push--porcelain.sh\nnew file mode 100644\nindex 0000000000..48f6fcc846\n--- /dev/null\n+++ b/t/t5411/test-0001-standard-git-push--porcelain.sh\n@@ -0,0 +1,147 @@\n+# Refs of upstream : master(A)  \n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push --atomic: master(A)  next(B)\n+test_expect_success \"git-push --atomic ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --atomic --porcelain origin \\\n+\t\tmaster \\\n+\t\t$B:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out |\n+\t\tsed -n \\\n+\t\t\t-e \"s/^# GETTEXT POISON #//\" \\\n+\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n+\t\t\t-e \"/^! / { s/   */ /g; p; }\" \\\n+\t\t\t>actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t! refs/heads/master:refs/heads/master [rejected] (non-fast-forward)\n+\t! <COMMIT-B>:refs/heads/next [rejected] (atomic push failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push         : master(A)  next(B)\n+test_expect_success \"non-fast-forward git-push ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git \\\n+\t\t-C workbench \\\n+\t\t-c advice.pushUpdateRejected=false \\\n+\t\tpush --porcelain origin \\\n+\t\tmaster \\\n+\t\t$B:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/next    <OID-A>..<OID-B>\n+\t!    refs/heads/master:refs/heads/master    [rejected] (non-fast-forward)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-B> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(B)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"git-push -f ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tmaster \\\n+\t\tmaster:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-B> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-B> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo <URL/of/upstream.git>\n+\t+    refs/heads/master:refs/heads/master    <OID-B>...<OID-A> (forced update)\n+\t-    :refs/heads/next    [deleted]\n+\t*    refs/tags/v123:refs/tags/v123    [new tag]\n+\t*    refs/heads/master:refs/review/master/topic    [new reference]\n+\t*    HEAD:refs/heads/a/b/c    [new branch]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\ndiff --git a/t/t5411/test-0002-pre-receive-declined.sh b/t/t5411/test-0002-pre-receive-declined.sh\nnew file mode 100644\nindex 0000000000..c246f7e68e\n--- /dev/null\n+++ b/t/t5411/test-0002-pre-receive-declined.sh\n@@ -0,0 +1,33 @@\n+test_expect_success \"setup pre-receive hook ($PROTOCOL)\" '\n+\tmv \"$upstream/hooks/pre-receive\" \"$upstream/hooks/pre-receive.ok\" &&\n+\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n+\texit 1\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push is declined ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] <COMMIT-B> -> master (pre-receive hook declined)\n+\t ! [remote rejected] HEAD -> next (pre-receive hook declined)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tmv \"$upstream/hooks/pre-receive.ok\" \"$upstream/hooks/pre-receive\"\n+'\ndiff --git a/t/t5411/test-0003-pre-receive-declined--porcelain.sh b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\nnew file mode 100644\nindex 0000000000..b14894de81\n--- /dev/null\n+++ b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\n@@ -0,0 +1,34 @@\n+test_expect_success \"setup pre-receive hook ($PROTOCOL/porcelain)\" '\n+\tmv \"$upstream/hooks/pre-receive\" \"$upstream/hooks/pre-receive.ok\" &&\n+\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n+\texit 1\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push is declined ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    <COMMIT-B>:refs/heads/master    [remote rejected] (pre-receive hook declined)\n+\t!    HEAD:refs/heads/next    [remote rejected] (pre-receive hook declined)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tmv \"$upstream/hooks/pre-receive.ok\" \"$upstream/hooks/pre-receive\"\n+'\n-- \n2.28.0\n\n"},{"id":"403769","messageId":"20200815171740.6257-5-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200518094039.757-1-worldhello.net@gmail.com","subject":"[PATCH v17 04/10] New capability \"report-status-v2\" for git-push","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-15T17:17:34Z","receivedAt":"2020-08-15T22:03:29Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThe new introduced \"proc-receive\" hook may handle a command for a\npseudo-reference with a zero-old as its old-oid, while the hook may\ncreate or update a reference with different name, different new-oid,\nand different old-oid (the reference may exist already with a non-zero\nold-oid).  Current \"report-status\" protocol cannot report the status for\nsuch reference rewrite.\n\nAdd new capability \"report-status-v2\" and new report protocol which is\nnot backward compatible for report of git-push.\n\nIf a user pushes to a pseudo-reference \"refs/for/master/topic\", and\n\"receive-pack\" creates two new references \"refs/changes/23/123/1\" and\n\"refs/changes/24/124/1\", for client without the knowledge of\n\"report-status-v2\", \"receive-pack\" will only send \"ok/ng\" directives in\nthe report, such as:\n\n    ok ref/for/master/topic\n\nBut for client which has the knowledge of \"report-status-v2\",\n\"receive-pack\" will use \"option\" directives to report more attributes\nfor the reference given by the above \"ok/ng\" directive.\n\n    ok refs/for/master/topic\n    option refname refs/changes/23/123/1\n    option new-oid <new-oid>\n    ok refs/for/master/topic\n    option refname refs/changes/24/124/1\n    option new-oid <new-oid>\n\nThe client will report two new created references to the end user.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSuggested-by: Jeff King <peff@peff.net>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c                        |  52 +++++++-\n builtin/send-pack.c                           |  21 +++-\n remote.c                                      |   4 +-\n remote.h                                      |   2 +-\n send-pack.c                                   |  99 ++++++++++++---\n t/t5411-proc-receive-hook.sh                  |   9 ++\n t/t5411/once-0010-report-status-v1.sh         |  89 +++++++++++++\n t/t5411/test-0032-report-with-options.sh      |  14 +--\n ...est-0033-report-with-options--porcelain.sh |  14 +--\n ...t-0036-report-multi-rewrite-for-one-ref.sh |  11 +-\n ...rt-multi-rewrite-for-one-ref--porcelain.sh |  11 +-\n t/t5411/test-0038-report-mixed-refs.sh        |   2 +-\n .../test-0039-report-mixed-refs--porcelain.sh |   2 +-\n transport-helper.c                            |  53 +++++++-\n transport.c                                   | 118 +++++++++++++-----\n 15 files changed, 424 insertions(+), 77 deletions(-)\n create mode 100644 t/t5411/once-0010-report-status-v1.sh\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex d0b33ee84b..58972aa7cf 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -57,6 +57,7 @@ static int advertise_push_options;\n static int unpack_limit = 100;\n static off_t max_input_size;\n static int report_status;\n+static int report_status_v2;\n static int use_sideband;\n static int use_atomic;\n static int use_push_options;\n@@ -240,7 +241,7 @@ static void show_ref(const char *path, const struct object_id *oid)\n \t\tstruct strbuf cap = STRBUF_INIT;\n \n \t\tstrbuf_addstr(&cap,\n-\t\t\t      \"report-status delete-refs side-band-64k quiet\");\n+\t\t\t      \"report-status report-status-v2 delete-refs side-band-64k quiet\");\n \t\tif (advertise_atomic_push)\n \t\t\tstrbuf_addstr(&cap, \" atomic\");\n \t\tif (prefer_ofs_delta)\n@@ -1918,6 +1919,8 @@ static struct command *read_head_info(struct packet_reader *reader,\n \t\t\tint len = 0;\n \t\t\tif (parse_feature_request(feature_list, \"report-status\"))\n \t\t\t\treport_status = 1;\n+\t\t\tif (parse_feature_request(feature_list, \"report-status-v2\"))\n+\t\t\t\treport_status_v2 = 1;\n \t\t\tif (parse_feature_request(feature_list, \"side-band-64k\"))\n \t\t\t\tuse_sideband = LARGE_PACKET_MAX;\n \t\t\tif (parse_feature_request(feature_list, \"quiet\"))\n@@ -2236,6 +2239,49 @@ static void report(struct command *commands, const char *unpack_status)\n \tstrbuf_release(&buf);\n }\n \n+static void report_v2(struct command *commands, const char *unpack_status)\n+{\n+\tstruct command *cmd;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ref_push_report_options *options;\n+\n+\tpacket_buf_write(&buf, \"unpack %s\\n\",\n+\t\t\t unpack_status ? unpack_status : \"ok\");\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tint count = 0;\n+\n+\t\tif (!cmd->report.error_message)\n+\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n+\t\t\t\t\t cmd->ref_name);\n+\t\telse\n+\t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n+\t\t\t\t\t cmd->ref_name,\n+\t\t\t\t\t cmd->report.error_message);\n+\t\tfor (options = cmd->report.options; options; options = options->next) {\n+\t\t\tif (count++ > 0)\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s\\n\", cmd->ref_name);\n+\t\t\tif (options->ref_name)\n+\t\t\t\tpacket_buf_write(&buf, \"option refname %s\\n\",\n+\t\t\t\t\t\t options->ref_name);\n+\t\t\tif (options->old_oid)\n+\t\t\t\tpacket_buf_write(&buf, \"option old-oid %s\\n\",\n+\t\t\t\t\t\t oid_to_hex(options->old_oid));\n+\t\t\tif (options->new_oid)\n+\t\t\t\tpacket_buf_write(&buf, \"option new-oid %s\\n\",\n+\t\t\t\t\t\t oid_to_hex(options->new_oid));\n+\t\t\tif (options->forced_update)\n+\t\t\t\tpacket_buf_write(&buf, \"option forced-update\\n\");\n+\t\t}\n+\t}\n+\tpacket_buf_flush(&buf);\n+\n+\tif (use_sideband)\n+\t\tsend_sideband(1, 1, buf.buf, buf.len, use_sideband);\n+\telse\n+\t\twrite_or_die(1, buf.buf, buf.len);\n+\tstrbuf_release(&buf);\n+}\n+\n static int delete_only(struct command *commands)\n {\n \tstruct command *cmd;\n@@ -2344,7 +2390,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\t\t\t &push_options);\n \t\tif (pack_lockfile)\n \t\t\tunlink_or_warn(pack_lockfile);\n-\t\tif (report_status)\n+\t\tif (report_status_v2)\n+\t\t\treport_v2(commands, unpack_status);\n+\t\telse if (report_status)\n \t\t\treport(commands, unpack_status);\n \t\trun_receive_hook(commands, \"post-receive\", 1,\n \t\t\t\t &push_options);\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 2b9610f121..9d0df751d6 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -29,10 +29,12 @@ static struct send_pack_args args;\n static void print_helper_status(struct ref *ref)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ref_push_report_options *options;\n \n \tfor (; ref; ref = ref->next) {\n \t\tconst char *msg = NULL;\n \t\tconst char *res;\n+\t\tint count = 0;\n \n \t\tswitch(ref->status) {\n \t\tcase REF_STATUS_NONE:\n@@ -86,14 +88,29 @@ static void print_helper_status(struct ref *ref)\n \n \t\tstrbuf_reset(&buf);\n \t\tstrbuf_addf(&buf, \"%s %s\", res, ref->name);\n-\t\tif (ref->remote_status)\n-\t\t\tmsg = ref->remote_status;\n+\t\tif (ref->report.error_message)\n+\t\t\tmsg = ref->report.error_message;\n \t\tif (msg) {\n \t\t\tstrbuf_addch(&buf, ' ');\n \t\t\tquote_two_c_style(&buf, \"\", msg, 0);\n \t\t}\n \t\tstrbuf_addch(&buf, '\\n');\n \n+\t\tfor (options = ref->report.options; options; options = options->next) {\n+\t\t\tif (count++ > 0)\n+\t\t\t\tstrbuf_addf(&buf, \"ok %s\\n\", ref->name);\n+\t\t\tif (options->ref_name)\n+\t\t\t\tstrbuf_addf(&buf, \"option refname %s\\n\",\n+\t\t\t\t\t    options->ref_name);\n+\t\t\tif (options->old_oid)\n+\t\t\t\tstrbuf_addf(&buf, \"option old-oid %s\\n\",\n+\t\t\t\t\t    oid_to_hex(options->old_oid));\n+\t\t\tif (options->new_oid)\n+\t\t\t\tstrbuf_addf(&buf, \"option new-oid %s\\n\",\n+\t\t\t\t\t    oid_to_hex(options->new_oid));\n+\t\t\tif (options->forced_update)\n+\t\t\t\tstrbuf_addstr(&buf, \"option forced-update\\n\");\n+\t\t}\n \t\twrite_or_die(1, buf.buf, buf.len);\n \t}\n \tstrbuf_release(&buf);\ndiff --git a/remote.c b/remote.c\nindex c5ed74f91c..682be35fb2 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -789,7 +789,7 @@ struct ref *copy_ref(const struct ref *ref)\n \tmemcpy(cpy, ref, len);\n \tcpy->next = NULL;\n \tcpy->symref = xstrdup_or_null(ref->symref);\n-\tcpy->remote_status = xstrdup_or_null(ref->remote_status);\n+\tcpy->report.error_message = xstrdup_or_null(ref->report.error_message);\n \tcpy->peer_ref = copy_ref(ref->peer_ref);\n \treturn cpy;\n }\n@@ -811,7 +811,7 @@ void free_one_ref(struct ref *ref)\n \tif (!ref)\n \t\treturn;\n \tfree_one_ref(ref->peer_ref);\n-\tfree(ref->remote_status);\n+\tfree((void *)ref->report.error_message);\n \tfree(ref->symref);\n \tfree(ref);\n }\ndiff --git a/remote.h b/remote.h\nindex ab2e86a2a5..1a7b53fc16 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -153,7 +153,7 @@ struct ref {\n \t\tREF_STATUS_EXPECTING_REPORT,\n \t\tREF_STATUS_ATOMIC_PUSH_FAILED\n \t} status;\n-\tchar *remote_status;\n+\tstruct ref_push_report report;\n \tstruct ref *peer_ref; /* when renaming */\n \tchar name[FLEX_ARRAY]; /* more */\n };\ndiff --git a/send-pack.c b/send-pack.c\nindex 632f1580ca..5e25352042 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -154,25 +154,79 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n {\n \tstruct ref *hint;\n \tint ret;\n+\tint new_options = 1;\n \n \thint = NULL;\n \tret = receive_unpack_status(reader);\n \twhile (1) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *head;\n \t\tconst char *refname;\n-\t\tchar *msg;\n+\t\tchar *p;\n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n \t\t\tbreak;\n-\t\tif (!starts_with(reader->line, \"ok \") && !starts_with(reader->line, \"ng \")) {\n-\t\t\terror(\"invalid ref status from remote: %s\", reader->line);\n+\t\thead = reader->line;\n+\t\tp = strchr(head, ' ');\n+\t\tif (!p) {\n+\t\t\terror(\"invalid status line from remote: %s\", reader->line);\n \t\t\tret = -1;\n \t\t\tbreak;\n \t\t}\n+\t\t*p++ = '\\0';\n+\n+\t\tif (!strcmp(head, \"option\")) {\n+\t\t\tstruct ref_push_report_options *options;\n+\t\t\tconst char *key, *val;\n+\n+\t\t\tif (!hint) {\n+\t\t\t       if (new_options) {\n+\t\t\t\t\terror(\"'option' without a matching 'ok/ng' directive\");\n+\t\t\t\t\tnew_options = 0;\n+\t\t\t\t}\n+\t\t\t\tret = -1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\toptions = hint->report.options;\n+\t\t\twhile (options && options->next)\n+\t\t\t\toptions = options->next;\n+\t\t\tif (new_options) {\n+\t\t\t\tif (!options) {\n+\t\t\t\t\thint->report.options = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\t\toptions = hint->report.options;\n+\t\t\t\t} else {\n+\t\t\t\t\toptions->next = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\t\toptions = options->next;\n+\t\t\t\t}\n+\t\t\t\tnew_options = 0;\n+\t\t\t}\n+\t\t\tassert(options);\n+\t\t\tkey = p;\n+\t\t\tp = strchr(key, ' ');\n+\t\t\tif (p)\n+\t\t\t\t*p++ = '\\0';\n+\t\t\tval = p;\n+\t\t\tif (!strcmp(key, \"refname\"))\n+\t\t\t\toptions->ref_name = xstrdup_or_null(val);\n+\t\t\telse if (!strcmp(key, \"old-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &old_oid, &val))\n+\t\t\t\toptions->old_oid = oiddup(&old_oid);\n+\t\t\telse if (!strcmp(key, \"new-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &new_oid, &val))\n+\t\t\t\toptions->new_oid = oiddup(&new_oid);\n+\t\t\telse if (!strcmp(key, \"forced-update\"))\n+\t\t\t\toptions->forced_update = 1;\n+\t\t\tcontinue;\n+\t\t}\n \n-\t\trefname = reader->line + 3;\n-\t\tmsg = strchr(refname, ' ');\n-\t\tif (msg)\n-\t\t\t*msg++ = '\\0';\n-\n+\t\tif (strcmp(head, \"ok\") && strcmp(head, \"ng\")) {\n+\t\t\terror(\"invalid ref status from remote: %s\", head);\n+\t\t\tret = -1;\n+\t\t\tbreak;\n+\t\t}\n+\t\trefname = p;\n+\t\tp = strchr(refname, ' ');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n \t\t/* first try searching at our hint, falling back to all refs */\n \t\tif (hint)\n \t\t\thint = find_ref_by_name(hint, refname);\n@@ -183,19 +237,24 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \t\t\t\t\trefname);\n \t\t\tcontinue;\n \t\t}\n-\t\tif (hint->status != REF_STATUS_EXPECTING_REPORT) {\n+\t\tif (hint->status != REF_STATUS_EXPECTING_REPORT &&\n+\t\t    hint->status != REF_STATUS_OK &&\n+\t\t    hint->status != REF_STATUS_REMOTE_REJECT) {\n \t\t\twarning(\"remote reported status on unexpected ref: %s\",\n \t\t\t\t\trefname);\n \t\t\tcontinue;\n \t\t}\n-\n-\t\tif (reader->line[0] == 'o' && reader->line[1] == 'k')\n-\t\t\thint->status = REF_STATUS_OK;\n-\t\telse\n+\t\tif (!strcmp(head, \"ng\")) {\n \t\t\thint->status = REF_STATUS_REMOTE_REJECT;\n-\t\thint->remote_status = xstrdup_or_null(msg);\n-\t\t/* start our next search from the next ref */\n-\t\thint = hint->next;\n+\t\t\tif (p)\n+\t\t\t\thint->report.error_message = xstrdup(p);\n+\t\t\telse\n+\t\t\t\thint->report.error_message = \"failed\";\n+\t\t} else {\n+\t\t\thint->status = REF_STATUS_OK;\n+\t\t\thint->report.message = xstrdup_or_null(p);\n+\t\t}\n+\t\tnew_options = 1;\n \t}\n \treturn ret;\n }\n@@ -371,7 +430,9 @@ int send_pack(struct send_pack_args *args,\n \tstruct packet_reader reader;\n \n \t/* Does the other end support the reporting? */\n-\tif (server_supports(\"report-status\"))\n+\tif (server_supports(\"report-status-v2\"))\n+\t\tstatus_report = 2;\n+\telse if (server_supports(\"report-status\"))\n \t\tstatus_report = 1;\n \tif (server_supports(\"delete-refs\"))\n \t\tallow_deleting_refs = 1;\n@@ -423,8 +484,10 @@ int send_pack(struct send_pack_args *args,\n \n \tuse_push_options = push_options_supported && args->push_options;\n \n-\tif (status_report)\n+\tif (status_report == 1)\n \t\tstrbuf_addstr(&cap_buf, \" report-status\");\n+\telse if (status_report == 2)\n+\t\tstrbuf_addstr(&cap_buf, \" report-status-v2\");\n \tif (use_sideband)\n \t\tstrbuf_addstr(&cap_buf, \" side-band-64k\");\n \tif (quiet_supported && (args->quiet || !args->progress))\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex 3a684353a8..746487286f 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -78,6 +78,15 @@ run_proc_receive_hook_test() {\n # Initialize the upstream repository and local workbench.\n setup_upstream_and_workbench\n \n+# Load test cases that only need to be executed once.\n+for t in  \"$TEST_DIRECTORY\"/t5411/once-*.sh\n+do\n+\t. \"$t\"\n+done\n+\n+# Initialize the upstream repository and local workbench.\n+setup_upstream_and_workbench\n+\n # Run test cases for 'proc-receive' hook on local file protocol.\n run_proc_receive_hook_test local\n \ndiff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh\nnew file mode 100644\nindex 0000000000..56a86d8ed4\n--- /dev/null\n+++ b/t/t5411/once-0010-report-status-v1.sh\n@@ -0,0 +1,89 @@\n+test_expect_success \"setup proc-receive hook\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic1\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/for/master/topic2\" \\\n+\t\t-r \"option refname refs/for/changes/23/123/1\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic2\" \\\n+\t\t-r \"option refname refs/for/changes/24/124/2\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\" \\\n+\t\t-r \"ng refs/for/next/topic target branch not exist\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic1(A)  foo(A)  refs/for/next/topic(A)  refs/for/master/topic2(A)\n+test_expect_success \"proc-receive: report status v1\" '\n+\t{\n+\t\tif test -z \"$GIT_DEFAULT_HASH\" || test \"$GIT_DEFAULT_HASH\" = \"sha1\"\n+\t\tthen\n+\t\t\tprintf \"%s %s refs/heads/master\\0report-status\\n\" \\\n+\t\t\t\t$A $B | packetize\n+\t\telse\n+\t\t\tprintf \"%s %s refs/heads/master\\0report-status object-format=$GIT_DEFAULT_HASH\\n\" \\\n+\t\t\t\t$A $B | packetize\n+\t\tfi &&\n+\t\tprintf \"%s %s refs/for/master/topic1\\n\" \\\n+\t\t\t$ZERO_OID $A | packetize &&\n+\t\tprintf \"%s %s refs/heads/foo\\n\" \\\n+\t\t\t$ZERO_OID $A | packetize &&\n+\t\tprintf \"%s %s refs/for/next/topic\\n\" \\\n+\t\t\t$ZERO_OID $A | packetize &&\n+\t\tprintf \"%s %s refs/for/master/topic2\\n\" \\\n+\t\t\t$ZERO_OID $A | packetize &&\n+\t\tprintf 0000 &&\n+\t\tprintf \"\" | git -C \"$upstream\" pack-objects --stdout\n+\t} | git receive-pack \"$upstream\" --stateless-rpc \\\n+\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t# pre-receive hook\n+\tpre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tpre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n+\tpre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tpre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tpre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2\n+\t# proc-receive hook\n+\tproc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n+\tproc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tproc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2\n+\tproc-receive> ok refs/for/master/topic1\n+\tproc-receive> option fall-through\n+\tproc-receive> ok refs/for/master/topic2\n+\tproc-receive> option refname refs/for/changes/23/123/1\n+\tproc-receive> option new-oid <COMMIT-A>\n+\tproc-receive> ok refs/for/master/topic2\n+\tproc-receive> option refname refs/for/changes/24/124/2\n+\tproc-receive> option old-oid <COMMIT-B>\n+\tproc-receive> option new-oid <COMMIT-A>\n+\tproc-receive> option forced-update\n+\tproc-receive> ng refs/for/next/topic target branch not exist\n+\t000eunpack ok\n+\t0019ok refs/heads/master\n+\t001eok refs/for/master/topic1\n+\t0016ok refs/heads/foo\n+\t0033ng refs/for/next/topic target branch not exist\n+\t001eok refs/for/master/topic2\n+\t0000# post-receive hook\n+\tpost-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n+\tpost-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/for/master/topic1\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nindex a743aa8018..3742ed3c3d 100644\n--- a/t/t5411/test-0032-report-with-options.sh\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -56,7 +56,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL)\" '\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -89,7 +89,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -123,7 +123,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL)\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-B>..<OID-A> HEAD -> refs/pull/123/head\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -155,7 +155,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL)\" '\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-B>..<OID-A> HEAD -> refs/for/master/topic\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -189,7 +189,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL)\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -241,9 +241,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \t * [new reference] HEAD -> refs/for/a/b/c/topic\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n \ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nindex 439b97b06e..f18ba9f06f 100644\n--- a/t/t5411/test-0033-report-with-options--porcelain.sh\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -57,7 +57,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL/porcelain)\"\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -92,7 +92,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -127,7 +127,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL/\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/pull/123/head    <OID-B>..<OID-A>\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -160,7 +160,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL/porcelain)\"\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-B>..<OID-A>\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -195,7 +195,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL/\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -249,9 +249,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porc\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/next/topic    [new reference]\n+\t*    HEAD:refs/pull/123/head    [new reference]\n \t*    HEAD:refs/for/a/b/c/topic    [new reference]\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t+    HEAD:refs/pull/124/head    <OID-B>...<OID-A> (forced update)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex 12acf9ea1f..64662afdc6 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -43,7 +43,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/24/124/1\n+\t <OID-A>..<OID-B> HEAD -> refs/changes/25/125/1\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -101,7 +103,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/24/124/1\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n+\t + <OID-B>...<OID-A> HEAD -> refs/changes/25/125/1 (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -147,7 +151,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/23/123/1\n+\t <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\ndiff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\nindex f4a2c56e14..a7e29af0c2 100644\n--- a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n@@ -43,7 +43,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n+\t*    HEAD:refs/changes/24/124/1    [new reference]\n+\t     HEAD:refs/changes/25/125/1    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n@@ -102,7 +104,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/changes/24/124/1    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n+\t+    HEAD:refs/changes/25/125/1    <OID-B>...<OID-A> (forced update)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n@@ -149,7 +153,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porc\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/changes/23/123/1    [new reference]\n+\t     HEAD:refs/changes/24/124/2    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nindex 1dd6a97a97..54bec4c026 100644\n--- a/t/t5411/test-0038-report-mixed-refs.sh\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -60,7 +60,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n \t * [new branch] HEAD -> baz\n \t * [new reference] HEAD -> refs/for/next/topic2\n \t * [new branch] HEAD -> foo\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \t ! [remote rejected] HEAD -> refs/for/next/topic1 (fail to call Web API)\n \t ! [remote rejected] HEAD -> refs/for/next/topic3 (proc-receive failed to report status)\n \tEOF\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nindex 32ebf63dcf..605b3cb5c9 100644\n--- a/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -60,7 +60,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcel\n \t*    HEAD:refs/heads/baz    [new branch]\n \t*    HEAD:refs/for/next/topic2    [new reference]\n \t*    HEAD:refs/heads/foo    [new branch]\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \t!    HEAD:refs/for/next/topic1    [remote rejected] (fail to call Web API)\n \t!    HEAD:refs/for/next/topic3    [remote rejected] (proc-receive failed to report status)\n \tDone\ndiff --git a/transport-helper.c b/transport-helper.c\nindex defafbf4c1..0029ba18bd 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -729,6 +729,49 @@ static int push_update_ref_status(struct strbuf *buf,\n {\n \tchar *refname, *msg;\n \tint status, forced = 0;\n+\tstatic struct ref *hint = NULL;\n+\tstatic int new_options = 1;\n+\n+\tif (starts_with(buf->buf, \"option \")) {\n+\t\tstruct ref_push_report_options *options;\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *key, *val;\n+\t\tchar *p;\n+\n+\t\tif (!hint)\n+\t\t\tdie(_(\"'option' without a matching 'ok/error' directive\"));\n+\t\toptions = hint->report.options;\n+\t\twhile (options && options->next)\n+\t\t\toptions = options->next;\n+\t\tif (new_options) {\n+\t\t\tif (!options) {\n+\t\t\t\thint->report.options = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\toptions = hint->report.options;\n+\t\t\t} else {\n+\t\t\t\toptions->next = xcalloc(1, sizeof(struct ref_push_report_options));\n+\t\t\t\toptions = options->next;\n+\t\t\t}\n+\t\t\tnew_options = 0;\n+\t\t}\n+\t\tassert(options);\n+\t\tkey = buf->buf + 7;\n+\t\tp = strchr(key, ' ');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n+\t\tval = p;\n+\t\tif (!strcmp(key, \"refname\"))\n+\t\t\toptions->ref_name = xstrdup_or_null(val);\n+\t\telse if (!strcmp(key, \"old-oid\") && val &&\n+\t\t\t !parse_oid_hex(val, &old_oid, &val))\n+\t\t\toptions->old_oid = oiddup(&old_oid);\n+\t\telse if (!strcmp(key, \"new-oid\") && val &&\n+\t\t\t !parse_oid_hex(val, &new_oid, &val))\n+\t\t\toptions->new_oid = oiddup(&new_oid);\n+\t\telse if (!strcmp(key, \"forced-update\"))\n+\t\t\toptions->forced_update = 1;\n+\t\t/* Not update remote namespace again. */\n+\t\treturn 1;\n+\t}\n \n \tif (starts_with(buf->buf, \"ok \")) {\n \t\tstatus = REF_STATUS_OK;\n@@ -791,8 +834,11 @@ static int push_update_ref_status(struct strbuf *buf,\n \t\t*ref = find_ref_by_name(remote_refs, refname);\n \tif (!*ref) {\n \t\twarning(_(\"helper reported unexpected status of %s\"), refname);\n+\t\thint = NULL;\n \t\treturn 1;\n \t}\n+\thint = *ref;\n+\tnew_options = 1;\n \n \tif ((*ref)->status != REF_STATUS_NONE) {\n \t\t/*\n@@ -805,7 +851,12 @@ static int push_update_ref_status(struct strbuf *buf,\n \n \t(*ref)->status = status;\n \t(*ref)->forced_update |= forced;\n-\t(*ref)->remote_status = msg;\n+\tif (msg) {\n+\t\tif (status == REF_STATUS_OK)\n+\t\t\t(*ref)->report.message = msg;\n+\t\telse\n+\t\t\t(*ref)->report.error_message = msg;\n+\t}\n \treturn !(status == REF_STATUS_OK);\n }\n \ndiff --git a/transport.c b/transport.c\nindex 19b033dc6a..10f7dbbcd0 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -460,13 +460,21 @@ void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int v\n \n static void print_ref_status(char flag, const char *summary,\n \t\t\t     struct ref *to, struct ref *from, const char *msg,\n+\t\t\t     struct ref_push_report_options *options,\n \t\t\t     int porcelain, int summary_width)\n {\n+\tconst char *to_name;\n+\n+\tif (options && options->ref_name)\n+\t\tto_name = options->ref_name;\n+\telse\n+\t\tto_name = to->name;\n+\n \tif (porcelain) {\n \t\tif (from)\n-\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to_name);\n \t\telse\n-\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to_name);\n \t\tif (msg)\n \t\t\tfprintf(stdout, \"%s (%s)\\n\", summary, msg);\n \t\telse\n@@ -480,9 +488,11 @@ static void print_ref_status(char flag, const char *summary,\n \t\tfprintf(stderr, \" %s%c %-*s%s \", red, flag, summary_width,\n \t\t\tsummary, reset);\n \t\tif (from)\n-\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(to->name));\n+\t\t\tfprintf(stderr, \"%s -> %s\",\n+\t\t\t\tprettify_refname(from->name),\n+\t\t\t\tprettify_refname(to_name));\n \t\telse\n-\t\t\tfputs(prettify_refname(to->name), stderr);\n+\t\t\tfputs(prettify_refname(to_name), stderr);\n \t\tif (msg) {\n \t\t\tfputs(\" (\", stderr);\n \t\t\tfputs(msg, stderr);\n@@ -492,27 +502,52 @@ static void print_ref_status(char flag, const char *summary,\n \t}\n }\n \n-static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_width)\n+static void print_ok_ref_status(struct ref *ref,\n+\t\t\t\tstruct ref_push_report_options *options,\n+\t\t\t\tint porcelain, int summary_width)\n {\n+\tstruct object_id *old_oid;\n+\tstruct object_id *new_oid;\n+\tconst char *ref_name;\n+\tint forced_update;\n+\n+\tif (options && options->old_oid)\n+\t\told_oid = options->old_oid;\n+\telse\n+\t\told_oid = &ref->old_oid;\n+\tif (options && options->new_oid)\n+\t\tnew_oid = options->new_oid;\n+\telse\n+\t\tnew_oid = &ref->new_oid;\n+\tif (options && options->forced_update)\n+\t\tforced_update = options->forced_update;\n+\telse\n+\t\tforced_update = ref->forced_update;\n+\tif (options && options->ref_name)\n+\t\tref_name = options->ref_name;\n+\telse\n+\t\tref_name = ref->name;\n+\n \tif (ref->deletion)\n \t\tprint_ref_status('-', \"[deleted]\", ref, NULL, NULL,\n-\t\t\t\t porcelain, summary_width);\n-\telse if (is_null_oid(&ref->old_oid))\n+\t\t\t\t options, porcelain, summary_width);\n+\telse if (is_null_oid(old_oid))\n \t\tprint_ref_status('*',\n-\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t (starts_with(ref_name, \"refs/tags/\")\n \t\t\t\t  ? \"[new tag]\"\n-\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t  : (starts_with(ref_name, \"refs/heads/\")\n \t\t\t\t     ? \"[new branch]\"\n \t\t\t\t     : \"[new reference]\")),\n-\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t ref, ref->peer_ref, NULL,\n+\t\t\t\t options, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n \t\tconst char *msg;\n \n-\t\tstrbuf_add_unique_abbrev(&quickref, &ref->old_oid,\n+\t\tstrbuf_add_unique_abbrev(&quickref, old_oid,\n \t\t\t\t\t DEFAULT_ABBREV);\n-\t\tif (ref->forced_update) {\n+\t\tif (forced_update) {\n \t\t\tstrbuf_addstr(&quickref, \"...\");\n \t\t\ttype = '+';\n \t\t\tmsg = \"forced update\";\n@@ -521,17 +556,18 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\t\ttype = ' ';\n \t\t\tmsg = NULL;\n \t\t}\n-\t\tstrbuf_add_unique_abbrev(&quickref, &ref->new_oid,\n+\t\tstrbuf_add_unique_abbrev(&quickref, new_oid,\n \t\t\t\t\t DEFAULT_ABBREV);\n \n \t\tprint_ref_status(type, quickref.buf, ref, ref->peer_ref, msg,\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t options, porcelain, summary_width);\n \t\tstrbuf_release(&quickref);\n \t}\n }\n \n-static int print_one_push_status(struct ref *ref, const char *dest, int count,\n-\t\t\t\t int porcelain, int summary_width)\n+static int _print_one_push_status(struct ref *ref, const char *dest, int count,\n+\t\t\t\t  struct ref_push_report_options *options,\n+\t\t\t\t  int porcelain, int summary_width)\n {\n \tif (!count) {\n \t\tchar *url = transport_anonymize_url(dest);\n@@ -542,65 +578,89 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count,\n \tswitch(ref->status) {\n \tcase REF_STATUS_NONE:\n \t\tprint_ref_status('X', \"[no match]\", ref, NULL, NULL,\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NODELETE:\n \t\tprint_ref_status('!', \"[rejected]\", ref, NULL,\n \t\t\t\t \"remote does not support deleting refs\",\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_UPTODATE:\n \t\tprint_ref_status('=', \"[up to date]\", ref,\n-\t\t\t\t ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t ref->peer_ref, NULL,\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NONFASTFORWARD:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"non-fast-forward\", porcelain, summary_width);\n+\t\t\t\t \"non-fast-forward\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"already exists\", porcelain, summary_width);\n+\t\t\t\t \"already exists\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_FETCH_FIRST:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"fetch first\", porcelain, summary_width);\n+\t\t\t\t \"fetch first\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NEEDS_FORCE:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"needs force\", porcelain, summary_width);\n+\t\t\t\t \"needs force\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_STALE:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"stale info\", porcelain, summary_width);\n+\t\t\t\t \"stale info\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_SHALLOW:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n \t\t\t\t \"new shallow roots not allowed\",\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REMOTE_REJECT:\n \t\tprint_ref_status('!', \"[remote rejected]\", ref,\n \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n-\t\t\t\t ref->remote_status, porcelain, summary_width);\n+\t\t\t\t ref->report.error_message,\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_EXPECTING_REPORT:\n \t\tprint_ref_status('!', \"[remote failure]\", ref,\n \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n \t\t\t\t \"remote failed to report status\",\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_ATOMIC_PUSH_FAILED:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"atomic push failed\", porcelain, summary_width);\n+\t\t\t\t \"atomic push failed\",\n+\t\t\t\t options, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_OK:\n-\t\tprint_ok_ref_status(ref, porcelain, summary_width);\n+\t\tprint_ok_ref_status(ref, options, porcelain, summary_width);\n \t\tbreak;\n \t}\n \n \treturn 1;\n }\n \n+static int print_one_push_status(struct ref *ref, const char *dest, int count,\n+\t\t\t\t int porcelain, int summary_width)\n+{\n+\tstruct ref_push_report_options *options;\n+\tint n = 0;\n+\n+\tif (!ref->report.options)\n+\t\treturn _print_one_push_status(ref, dest, count,\n+\t\t\t\t\t      NULL, porcelain, summary_width);\n+\n+\tfor (options = ref->report.options; options; options = options->next)\n+\t\t_print_one_push_status(ref, dest, count + n++,\n+\t\t\t\t       options, porcelain, summary_width);\n+\treturn n;\n+}\n+\n static int measure_abbrev(const struct object_id *oid, int sofar)\n {\n \tchar hex[GIT_MAX_HEXSZ + 1];\n-- \n2.28.0\n\n"},{"id":"403770","messageId":"20200815171740.6257-10-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200518094039.757-1-worldhello.net@gmail.com","subject":"[PATCH v17 09/10] transport: parse report options for tracking refs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-15T17:17:39Z","receivedAt":"2020-08-15T22:03:31Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing a pseudo reference (such as \"refs/for/master/topic\"), may\ncreate or update one or more references.  The real names of the\nreferences will be stored in the report options.  Parse report options\nto create or update remote-tracking branches properly.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 22 +++++++---\n transport.c                                   | 40 ++++++++++++++-----\n 2 files changed, 46 insertions(+), 16 deletions(-)\n\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex 8eec3a1ed8..73283d81e8 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -78,10 +78,14 @@ test_expect_success \"proc-receive: check remote-tracking #1 ($PROTOCOL)\" '\n \t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n-\t<COMMIT-A> refs/t/for/master/topic\n+\t<COMMIT-A> refs/t/changes/24/124/1\n+\t<COMMIT-B> refs/t/changes/25/125/1\n+\t<COMMIT-B> refs/t/for/master/topic\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C workbench update-ref -d refs/t/for/master/topic\n+\tgit -C workbench update-ref -d refs/t/for/master/topic &&\n+\tgit -C workbench update-ref -d refs/t/changes/24/124/1 &&\n+\tgit -C workbench update-ref -d refs/t/changes/25/125/1\n '\n \n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)\" '\n@@ -151,10 +155,14 @@ test_expect_success \"proc-receive: check remote-tracking #2 ($PROTOCOL)\" '\n \t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n-\t<COMMIT-A> refs/t/for/master/topic\n+\t<COMMIT-A> refs/t/changes/24/124/1\n+\t<COMMIT-A> refs/t/changes/25/125/1\n+\t<COMMIT-B> refs/t/for/master/topic\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C workbench update-ref -d refs/t/for/master/topic\n+\tgit -C workbench update-ref -d refs/t/for/master/topic &&\n+\tgit -C workbench update-ref -d refs/t/changes/24/124/1 &&\n+\tgit -C workbench update-ref -d refs/t/changes/25/125/1\n '\n \n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)\" '\n@@ -210,8 +218,10 @@ test_expect_success \"proc-receive: check remote-tracking #3 ($PROTOCOL)\" '\n \t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n-\t<COMMIT-A> refs/t/for/master/topic\n+\t<COMMIT-A> refs/t/changes/23/123/1\n+\t<COMMIT-B> refs/t/changes/24/124/2\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C workbench update-ref -d refs/t/for/master/topic\n+\tgit -C workbench update-ref -d refs/t/changes/24/124/1 &&\n+\tgit -C workbench update-ref -d refs/t/changes/25/125/2\n '\ndiff --git a/transport.c b/transport.c\nindex 10f7dbbcd0..fe0c73fa88 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -436,28 +436,48 @@ int transport_refs_pushed(struct ref *ref)\n \treturn 0;\n }\n \n-void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose)\n+static void update_tracking_ref(struct remote *remote, char *refname,\n+\t\t\t\tstruct object_id *new_oid, int deletion,\n+\t\t\t\tint verbose)\n {\n \tstruct refspec_item rs;\n \n-\tif (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE)\n-\t\treturn;\n-\n-\trs.src = ref->name;\n+\trs.src = refname;\n \trs.dst = NULL;\n-\n \tif (!remote_find_tracking(remote, &rs)) {\n \t\tif (verbose)\n \t\t\tfprintf(stderr, \"updating local tracking ref '%s'\\n\", rs.dst);\n-\t\tif (ref->deletion) {\n+\t\tif (deletion)\n \t\t\tdelete_ref(NULL, rs.dst, NULL, 0);\n-\t\t} else\n-\t\t\tupdate_ref(\"update by push\", rs.dst, &ref->new_oid,\n-\t\t\t\t   NULL, 0, 0);\n+\t\telse\n+\t\t\tupdate_ref(\"update by push\", rs.dst, new_oid,\n+\t\t\t\tNULL, 0, 0);\n \t\tfree(rs.dst);\n \t}\n }\n \n+void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose)\n+{\n+\tchar *refname;\n+\tstruct object_id *new_oid;\n+\tstruct ref_push_report_options *options;\n+\n+\tif (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE)\n+\t\treturn;\n+\n+\toptions = ref->report.options;\n+\tif (!options)\n+\t\tupdate_tracking_ref(remote, ref->name, &ref->new_oid,\n+\t\t\t\t    ref->deletion, verbose);\n+\telse\n+\t\tfor (; options; options = options->next) {\n+\t\t\trefname = options->ref_name ? (char *)options->ref_name : ref->name;\n+\t\t\tnew_oid = options->new_oid ? options->new_oid : &ref->new_oid;\n+\t\t\tupdate_tracking_ref(remote, refname, new_oid,\n+\t\t\t\t\t    is_null_oid(new_oid), verbose);\n+\t\t}\n+}\n+\n static void print_ref_status(char flag, const char *summary,\n \t\t\t     struct ref *to, struct ref *from, const char *msg,\n \t\t\t     struct ref_push_report_options *options,\n-- \n2.28.0\n\n"},{"id":"403773","messageId":"20200815171740.6257-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200518094039.757-1-worldhello.net@gmail.com","subject":"[PATCH v17 00/10] New proc-receive hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-15T17:17:30Z","receivedAt":"2020-08-15T22:05:11Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThis topic introduces a new hook named \"proc-receive\" for\n\"receive-pack\".  We can use this hook to implement a Gerrit-like\ncentralized workflow.\n\n## Changes since v16\n\n1. Fixes test helper for proc-receive which is not compatible with sha256.\n\n   Run `setup_git_directory_gently()`  in `t/helper/test-proc-receive.c`,\n   so that `the_repository` variable is initialized with proper hash_algo.\n\n   See range-diff:\n\n         3:  6ac2e0f88c !  3:  94d3053662 receive-pack: add new proc-receive hook\n            @@ builtin/receive-pack.c: static void run_update_post_hook(struct command *command\n             -\t\tif (cmd->error_string || cmd->did_not_exist)\n             +\t\tif (cmd->report.error_message || cmd->did_not_exist)\n              \t\t\tcontinue;\n            - \t\tif (!proc.args.argc)\n            - \t\t\targv_array_push(&proc.args, hook);\n            + \t\tif (!proc.args.nr)\n            + \t\t\tstrvec_push(&proc.args, hook);\n             @@ builtin/receive-pack.c: static void check_aliased_update_internal(struct command *cmd,\n              \tif (!dst_name) {\n              \t\trp_error(\"refusing update to broken symref '%s'\", cmd->ref_name);\n            @@ t/helper/test-proc-receive.c (new)\n             +\n             +int cmd__proc_receive(int argc, const char **argv)\n             +{\n            ++\tint nongit_ok = 0;\n             +\tstruct packet_reader reader;\n             +\tstruct command *commands = NULL;\n             +\tstruct string_list push_options = STRING_LIST_INIT_DUP;\n            @@ t/helper/test-proc-receive.c (new)\n             +\t\tOPT_END()\n             +\t};\n             +\n            ++\tsetup_git_directory_gently(&nongit_ok);\n            ++\n             +\targc = parse_options(argc, argv, \"test-tools\", options, proc_receive_usage, 0);\n             +\tif (argc > 0)\n             +\t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n            -+\n             +\tpacket_reader_init(&reader, 0, NULL, 0,\n             +\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n             +\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n\n2. Fixed broken test cases which is not compatible with sha256.\n\n   Add capability \"object-format=$GIT_DEFAULT_HASH\" in\n   \"t/t5411/once-0010-report-status-v1.sh\". See range-diff:\n\n        4:  00c9eee210 !  4:  215d786adc New capability \"report-status-v2\" for git-push\n           @@ builtin/receive-pack.c: static void show_ref(const char *path, const struct obje\n             \t\t\tstrbuf_addstr(&cap, \" atomic\");\n             \t\tif (prefer_ofs_delta)\n            @@ builtin/receive-pack.c: static struct command *read_head_info(struct packet_reader *reader,\n           - \t\t\tconst char *feature_list = reader->line + linelen + 1;\n           + \t\t\tint len = 0;\n             \t\t\tif (parse_feature_request(feature_list, \"report-status\"))\n             \t\t\t\treport_status = 1;\n            +\t\t\tif (parse_feature_request(feature_list, \"report-status-v2\"))\n           @@ t/t5411/once-0010-report-status-v1.sh (new)\n            +# git push         : (B)                   refs/for/master/topic1(A)  foo(A)  refs/for/next/topic(A)  refs/for/master/topic2(A)\n            +test_expect_success \"proc-receive: report status v1\" '\n            +\t{\n           -+\t\tprintf \"%s %s refs/heads/master\\0report-status\\n\" \\\n           -+\t\t\t$A $B | packetize &&\n           ++\t\tif test -z \"$GIT_DEFAULT_HASH\" || test \"$GIT_DEFAULT_HASH\" = \"sha1\"\n           ++\t\tthen\n           ++\t\t\tprintf \"%s %s refs/heads/master\\0report-status\\n\" \\\n           ++\t\t\t\t$A $B | packetize\n           ++\t\telse\n           ++\t\t\tprintf \"%s %s refs/heads/master\\0report-status object-format=$GIT_DEFAULT_HASH\\n\" \\\n           ++\t\t\t\t$A $B | packetize\n           ++\t\tfi &&\n            +\t\tprintf \"%s %s refs/for/master/topic1\\n\" \\\n            +\t\t\t$ZERO_OID $A | packetize &&\n            +\t\tprintf \"%s %s refs/heads/foo\\n\" \\\n\n3. Add optional modifiers for config variable \"receive.procReceiveRefs\".\n   See range-diff:\n\n        7:  97704cbcc6 <  -:  ---------- refs.c: refactor to reuse ref_is_hidden()\n        8:  415677f838 !  7:  08b176a61a receive-pack: new config receive.procReceiveRefs\n           @@ Commit message\n                    git config --system --add receive.procReceiveRefs refs/for\n                    git config --system --add receive.procReceiveRefs refs/drafts\n            \n           -    If the specific prefix strings match the reference names of the commands\n           -    which are sent from git client to `receive-pack`, these commands will be\n           -    executed by an external hook (named \"proc-receive\"), instead of the\n           -    internal `execute_commands` function.\n           +    If the specific prefix strings given by the config variables match the\n           +    reference names of the commands which are sent from git client to\n           +    `receive-pack`, these commands will be executed by an external hook\n           +    (named \"proc-receive\"), instead of the internal `execute_commands`\n           +    function.\n            \n                For example, if it is set to \"refs/for\", pushing to a reference such as\n                \"refs/for/master\" will not create or update reference \"refs/for/master\",\n                but may create or update a pull request directly by running the hook\n                \"proc-receive\".\n            \n           +    Optional modifiers can be provided in the beginning of value of the\n           +    config variable to filter commands for specific actions: create (a),\n           +    mofify (m), delete (d). E.g.:\n           +\n           +        git config --system --add receive.procReceiveRefs ad:refs/heads\n           +\n                Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n            \n             ## Documentation/config/receive.txt ##\n           @@ Documentation/config/receive.txt: receive.hideRefs::\n            +\tvariable is not defined, the \"proc-receive\" hook will never be\n            +\tused, and all commands will be executed by the internal\n            +\t`execute_commands` function.\n           -+\n           -+\tFor example, if this variable is set to \"refs/for\", pushing to\n           -+\treference such as \"refs/for/master\" will not create or update a\n           -+\treference named \"refs/for/master\", but may create or update a\n           -+\tpull request directly by running the hook \"proc-receive\".\n           -+\n           +++\n           ++For example, if this variable is set to \"refs/for\", pushing to reference\n           ++such as \"refs/for/master\" will not create or update a reference named\n           ++\"refs/for/master\", but may create or update a pull request directly by\n           ++running the hook \"proc-receive\".\n           +++\n           ++Optional modifiers can be provided in the beginning of the value to filter\n           ++commands for specific actions: create (a), mofify (m), delete (d). E.g.:\n           +++\n           ++\tgit config --system --add receive.procReceiveRefs ad:refs/heads\n           ++ \n             receive.updateServerInfo::\n             \tIf set to true, git-receive-pack will run git-update-server-info\n             \tafter receiving data from git-push and updating refs.\n            \n             ## builtin/receive-pack.c ##\n           -@@ builtin/receive-pack.c: static struct object_id push_cert_oid;\n           - static struct signature_check sigcheck;\n           - static const char *push_cert_nonce;\n           - static const char *cert_nonce_seed;\n           -+static struct string_list proc_receive_refs;\n           +@@ builtin/receive-pack.c: static int keepalive_in_sec = 5;\n             \n           - static const char *NONCE_UNSOLICITED = \"UNSOLICITED\";\n           - static const char *NONCE_BAD = \"BAD\";\n           + static struct tmp_objdir *tmp_objdir;\n           + \n           ++static struct proc_receive_ref {\n           ++\tunsigned int want_add:1,\n           ++\t\t     want_delete:1,\n           ++\t\t     want_modify:1;\n           ++\tchar *ref_prefix;\n           ++\tstruct proc_receive_ref *next;\n           ++} *proc_receive_ref = NULL;\n           ++\n           ++static void proc_receive_ref_append(const char *prefix);\n           ++\n           + static enum deny_action parse_deny_action(const char *var, const char *value)\n           + {\n           + \tif (value) {\n            @@ builtin/receive-pack.c: static int receive_pack_config(const char *var, const char *value, void *cb)\n             \t\treturn 0;\n             \t}\n             \n            +\tif (strcmp(var, \"receive.procreceiverefs\") == 0) {\n           -+\t\tchar *prefix;\n           -+\t\tint len;\n           -+\n            +\t\tif (!value)\n            +\t\t\treturn config_error_nonbool(var);\n           -+\t\tprefix = xstrdup(value);\n           -+\t\tlen = strlen(prefix);\n           -+\t\twhile (len && prefix[len - 1] == '/')\n           -+\t\t\tprefix[--len] = '\\0';\n           -+\t\tstring_list_append(&proc_receive_refs, prefix);\n           ++\t\tproc_receive_ref_append(value);\n            +\t\treturn 0;\n            +\t}\n            +\n             \treturn git_default_config(var, value, cb);\n             }\n             \n           +@@ builtin/receive-pack.c: struct command {\n           + \tchar ref_name[FLEX_ARRAY]; /* more */\n           + };\n           + \n           ++static void proc_receive_ref_append(const char *prefix)\n           ++{\n           ++\tstruct proc_receive_ref *ref_pattern;\n           ++\tchar *p;\n           ++\tint len;\n           ++\n           ++\tref_pattern = xcalloc(1, sizeof(struct proc_receive_ref));\n           ++\tp = strchr(prefix, ':');\n           ++\tif (p) {\n           ++\t\twhile (prefix < p) {\n           ++\t\t\tif (*prefix == 'a')\n           ++\t\t\t\tref_pattern->want_add = 1;\n           ++\t\t\telse if (*prefix == 'd')\n           ++\t\t\t\tref_pattern->want_delete = 1;\n           ++\t\t\telse if (*prefix == 'm')\n           ++\t\t\t\tref_pattern->want_modify = 1;\n           ++\t\t\tprefix++;\n           ++\t\t}\n           ++\t\tprefix++;\n           ++\t} else {\n           ++\t\tref_pattern->want_add = 1;\n           ++\t\tref_pattern->want_delete = 1;\n           ++\t\tref_pattern->want_modify = 1;\n           ++\t}\n           ++\tref_pattern->next = NULL;\n           ++\tref_pattern->ref_prefix = xstrdup(prefix);\n           ++\tlen = strlen(ref_pattern->ref_prefix);\n           ++\twhile (len && ref_pattern->ref_prefix[len - 1] == '/')\n           ++\t\tref_pattern->ref_prefix[--len] = '\\0';\n           ++\tif (proc_receive_ref == NULL) {\n           ++\t\tproc_receive_ref = ref_pattern;\n           ++\t} else {\n           ++\t\tstruct proc_receive_ref *end;\n           ++\n           ++\t\tend = proc_receive_ref;\n           ++\t\twhile (end->next)\n           ++\t\t\tend = end->next;\n           ++\t\tend->next = ref_pattern;\n           ++\t}\n           ++}\n           ++\n           ++static int proc_receive_ref_matches(struct command *cmd)\n           ++{\n           ++\tstruct proc_receive_ref *p;\n           ++\n           ++\tif (!proc_receive_ref)\n           ++\t\treturn 0;\n           ++\n           ++\tfor (p = proc_receive_ref; p; p = p->next) {\n           ++\t\tconst char *match = p->ref_prefix;\n           ++\t\tint neg = 0;\n           ++\t\tconst char *remains;\n           ++\n           ++\t\tif (!p->want_add && is_null_oid(&cmd->old_oid))\n           ++\t\t\tcontinue;\n           ++\t\telse if (!p->want_delete && is_null_oid(&cmd->new_oid))\n           ++\t\t\tcontinue;\n           ++\t\telse if (!p->want_modify &&\n           ++\t\t\t !is_null_oid(&cmd->old_oid) &&\n           ++\t\t\t !is_null_oid(&cmd->new_oid))\n           ++\t\t\tcontinue;\n           ++\n           ++\t\tif (*match == '!') {\n           ++\t\t\tneg = 1;\n           ++\t\t\tmatch++;\n           ++\t\t}\n           ++\n           ++\t\tif (skip_prefix(cmd->ref_name, match, &remains) &&\n           ++\t\t    (!*remains || *remains == '/')) {\n           ++\t\t\tif (!neg)\n           ++\t\t\t\treturn 1;\n           ++\t\t} else if (neg) {\n           ++\t\t\treturn 1;\n           ++\t\t}\n           ++\t}\n           ++\treturn 0;\n           ++}\n           ++\n           + static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2)));\n           + static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2)));\n           + \n            @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n             \t * Try to find commands that have special prefix in their reference names,\n             \t * and mark them to run an external \"proc-receive\" hook later.\n           @@ builtin/receive-pack.c: static void execute_commands(struct command *commands,\n            -\tfor (cmd = commands; cmd; cmd = cmd->next) {\n            -\t\tif (!should_process_cmd(cmd))\n            -\t\t\tcontinue;\n           -+\tif (proc_receive_refs.nr > 0) {\n           -+\t\tstruct strbuf refname_full = STRBUF_INIT;\n           -+\t\tsize_t prefix_len;\n           -+\n           -+\t\tstrbuf_addstr(&refname_full, get_git_namespace());\n           -+\t\tprefix_len = refname_full.len;\n           ++\tif (proc_receive_ref) {\n           ++\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n           ++\t\t\tif (!should_process_cmd(cmd))\n           ++\t\t\t\tcontinue;\n             \n            -\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n            -\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n            -\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n            -\t\t\trun_proc_receive = 1;\n           -+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n           -+\t\t\tif (!should_process_cmd(cmd))\n           -+\t\t\t\tcontinue;\n           -+\n           -+\t\t\tstrbuf_setlen(&refname_full, prefix_len);\n           -+\t\t\tstrbuf_addstr(&refname_full, cmd->ref_name);\n           -+\t\t\tif (ref_matches(&proc_receive_refs, cmd->ref_name, refname_full.buf)) {\n           ++\t\t\tif (proc_receive_ref_matches(cmd)) {\n            +\t\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n            +\t\t\t\trun_proc_receive = 1;\n            +\t\t\t}\n             \t\t}\n           -+\n           -+\t\tstrbuf_release(&refname_full);\n             \t}\n             \n           - \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n           -@@ builtin/receive-pack.c: int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n           - \t\tOPT_END()\n           - \t};\n           - \n           -+\tstring_list_init(&proc_receive_refs, 0);\n           -+\n           - \tpacket_trace_identity(\"receive-pack\");\n           - \n           - \targc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);\n           -@@ builtin/receive-pack.c: int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n           - \toid_array_clear(&shallow);\n           - \toid_array_clear(&ref);\n           - \tfree((void *)push_cert_nonce);\n           -+\tstring_list_clear(&proc_receive_refs, 0);\n           - \treturn 0;\n           - }\n            \n             ## t/t5411/once-0010-report-status-v1.sh ##\n            @@\n           @@ t/t5411/test-0041-process-all-refs--porcelain.sh (new)\n            +\t\tgit update-ref -d refs/heads/baz\n            +\t)\n            +'\n           +\n           + ## t/t5411/test-0050-proc-receive-refs.sh (new) ##\n           +@@\n           ++test_expect_success \"config receive.procReceiveRefs with modifiers ($PROTOCOL)\" '\n           ++\t(\n           ++\t\tcd \"$upstream\" &&\n           ++\t\tgit config --unset-all receive.procReceiveRefs &&\n           ++\t\tgit config --add receive.procReceiveRefs m:refs/heads/master &&\n           ++\t\tgit config --add receive.procReceiveRefs ad:refs/heads &&\n           ++\t\tgit config --add receive.procReceiveRefs \"a:!refs/heads\"\n           ++\t)\n           ++'\n           ++\n           ++test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n           ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n           ++\tprintf >&2 \"# proc-receive hook\\n\"\n           ++\ttest-tool proc-receive -v \\\n           ++\t\t-r \"ok refs/heads/master\" \\\n           ++\t\t-r \"option refname refs/pull/123/head\" \\\n           ++\t\t-r \"option old-oid $A\" \\\n           ++\t\t-r \"option new-oid $B\" \\\n           ++\t\t-r \"ok refs/tags/v123 \" \\\n           ++\t\t-r \"option refname refs/pull/124/head\"\n           ++\tEOF\n           ++'\n           ++\n           ++# Refs of upstream : master(A)\n           ++# Refs of workbench: master(A)  tags/v123\n           ++# git push         : master(B)  tags/v123\n           ++test_expect_success \"proc-receive: update branch and new tag ($PROTOCOL)\" '\n           ++\tgit -C workbench push origin \\\n           ++\t\t$B:refs/heads/master \\\n           ++\t\tv123 >out 2>&1 &&\n           ++\tmake_user_friendly_and_stable_output <out >actual &&\n           ++\tcat >expect <<-EOF &&\n           ++\tremote: # pre-receive hook\n           ++\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n           ++\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n           ++\tremote: # proc-receive hook\n           ++\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n           ++\tremote: proc-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n           ++\tremote: proc-receive> ok refs/heads/master\n           ++\tremote: proc-receive> option refname refs/pull/123/head\n           ++\tremote: proc-receive> option old-oid <COMMIT-A>\n           ++\tremote: proc-receive> option new-oid <COMMIT-B>\n           ++\tremote: proc-receive> ok refs/tags/v123\n           ++\tremote: proc-receive> option refname refs/pull/124/head\n           ++\tremote: # post-receive hook\n           ++\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head\n           ++\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/pull/124/head\n           ++\tTo <URL/of/upstream.git>\n           ++\t <OID-A>..<OID-B> <COMMIT-B> -> refs/pull/123/head\n           ++\t * [new reference] v123 -> refs/pull/124/head\n           ++\tEOF\n           ++\ttest_cmp expect actual &&\n           ++\tgit -C \"$upstream\" show-ref >out &&\n           ++\tmake_user_friendly_and_stable_output <out >actual &&\n           ++\tcat >expect <<-EOF &&\n           ++\t<COMMIT-A> refs/heads/master\n           ++\tEOF\n           ++\ttest_cmp expect actual\n           ++'\n           ++\n           ++# Refs of upstream : master(A)\n           ++# Refs of workbench: master(A)  tags/v123\n           ++test_expect_success \"setup upstream: create tags/v123 ($PROTOCOL)\" '\n           ++\tgit -C \"$upstream\" update-ref refs/tags/v123 $TAG &&\n           ++\tgit -C \"$upstream\" show-ref >out &&\n           ++\tmake_user_friendly_and_stable_output <out >actual &&\n           ++\tcat >expect <<-EOF &&\n           ++\t<COMMIT-A> refs/heads/master\n           ++\t<TAG-v123> refs/tags/v123\n           ++\tEOF\n           ++\ttest_cmp expect actual\n           ++'\n           ++\n           ++test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n           ++\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n           ++\tprintf >&2 \"# proc-receive hook\\n\"\n           ++\ttest-tool proc-receive -v \\\n           ++\t\t-r \"ok refs/heads/master\" \\\n           ++\t\t-r \"option refname refs/pull/123/head\" \\\n           ++\t\t-r \"option old-oid $A\" \\\n           ++\t\t-r \"option new-oid $ZERO_OID\" \\\n           ++\t\t-r \"ok refs/heads/next\" \\\n           ++\t\t-r \"option refname refs/pull/124/head\" \\\n           ++\t\t-r \"option new-oid $A\"\n           ++\tEOF\n           ++'\n           ++\n           ++# Refs of upstream : master(A)  tags/v123\n           ++# Refs of workbench: master(A)  tags/v123\n           ++# git push         : NULL       NULL       next(A)\n           ++test_expect_success \"proc-receive: create/delete branch, and delete tag ($PROTOCOL)\" '\n           ++\tgit -C workbench push origin \\\n           ++\t\t:refs/heads/master \\\n           ++\t\t$A:refs/heads/next \\\n           ++\t\t:refs/tags/v123 >out 2>&1 &&\n           ++\tmake_user_friendly_and_stable_output <out >actual &&\n           ++\tcat >expect <<-EOF &&\n           ++\tremote: # pre-receive hook\n           ++\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/master\n           ++\tremote: pre-receive< <TAG-v123> <ZERO-OID> refs/tags/v123\n           ++\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n           ++\tremote: # proc-receive hook\n           ++\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/master\n           ++\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n           ++\tremote: proc-receive> ok refs/heads/master\n           ++\tremote: proc-receive> option refname refs/pull/123/head\n           ++\tremote: proc-receive> option old-oid <COMMIT-A>\n           ++\tremote: proc-receive> option new-oid <ZERO-OID>\n           ++\tremote: proc-receive> ok refs/heads/next\n           ++\tremote: proc-receive> option refname refs/pull/124/head\n           ++\tremote: proc-receive> option new-oid <COMMIT-A>\n           ++\tremote: # post-receive hook\n           ++\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/pull/123/head\n           ++\tremote: post-receive< <TAG-v123> <ZERO-OID> refs/tags/v123\n           ++\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/124/head\n           ++\tTo <URL/of/upstream.git>\n           ++\t - [deleted] refs/pull/123/head\n           ++\t - [deleted] v123\n           ++\t * [new reference] <COMMIT-A> -> refs/pull/124/head\n           ++\tEOF\n           ++\ttest_cmp expect actual &&\n           ++\tgit -C \"$upstream\" show-ref >out &&\n           ++\tmake_user_friendly_and_stable_output <out >actual &&\n           ++\tcat >expect <<-EOF &&\n           ++\t<COMMIT-A> refs/heads/master\n           ++\tEOF\n           ++\ttest_cmp expect actual\n           ++'\n\n---\n\nJiang Xin (10):\n  transport: not report a non-head push as a branch\n  t5411: add basic test cases for proc-receive hook\n  receive-pack: add new proc-receive hook\n  New capability \"report-status-v2\" for git-push\n  doc: add document for capability report-status-v2\n  receive-pack: feed report options to post-receive\n  receive-pack: new config receive.procReceiveRefs\n  t5411: test updates of remote-tracking branches\n  transport: parse report options for tracking refs\n  doc: add documentation for the proc-receive hook\n\n Documentation/config/receive.txt              |  19 +\n Documentation/githooks.txt                    |  62 ++\n Documentation/technical/pack-protocol.txt     |  43 +-\n .../technical/protocol-capabilities.txt       |  17 +-\n Makefile                                      |   1 +\n builtin/receive-pack.c                        | 528 ++++++++++++++++--\n builtin/send-pack.c                           |  21 +-\n remote.c                                      |   4 +-\n remote.h                                      |  16 +-\n send-pack.c                                   |  99 +++-\n t/helper/test-proc-receive.c                  | 176 ++++++\n t/helper/test-tool.c                          |   1 +\n t/helper/test-tool.h                          |   1 +\n t/t5411-proc-receive-hook.sh                  | 117 ++++\n t/t5411/common-functions.sh                   |  56 ++\n t/t5411/once-0010-report-status-v1.sh         |  94 ++++\n t/t5411/test-0000-standard-git-push.sh        | 143 +++++\n .../test-0001-standard-git-push--porcelain.sh | 147 +++++\n t/t5411/test-0002-pre-receive-declined.sh     |  33 ++\n ...st-0003-pre-receive-declined--porcelain.sh |  34 ++\n t/t5411/test-0010-proc-receive-settings.sh    |   7 +\n t/t5411/test-0011-no-hook-error.sh            |  64 +++\n t/t5411/test-0012-no-hook-error--porcelain.sh |  66 +++\n t/t5411/test-0013-bad-protocol.sh             | 217 +++++++\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 160 ++++++\n t/t5411/test-0020-report-ng.sh                |  67 +++\n t/t5411/test-0021-report-ng--porcelain.sh     |  69 +++\n t/t5411/test-0022-report-unexpect-ref.sh      |  45 ++\n ...est-0023-report-unexpect-ref--porcelain.sh |  46 ++\n t/t5411/test-0024-report-unknown-ref.sh       |  34 ++\n ...test-0025-report-unknown-ref--porcelain.sh |  35 ++\n t/t5411/test-0026-push-options.sh             |  79 +++\n t/t5411/test-0027-push-options--porcelain.sh  |  82 +++\n t/t5411/test-0030-report-ok.sh                |  35 ++\n t/t5411/test-0031-report-ok--porcelain.sh     |  36 ++\n t/t5411/test-0032-report-with-options.sh      | 256 +++++++++\n ...est-0033-report-with-options--porcelain.sh | 265 +++++++++\n t/t5411/test-0034-report-ft.sh                |  44 ++\n t/t5411/test-0035-report-ft--porcelain.sh     |  45 ++\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 227 ++++++++\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 172 ++++++\n t/t5411/test-0038-report-mixed-refs.sh        |  89 +++\n .../test-0039-report-mixed-refs--porcelain.sh |  91 +++\n t/t5411/test-0040-process-all-refs.sh         | 113 ++++\n .../test-0041-process-all-refs--porcelain.sh  | 114 ++++\n t/t5411/test-0050-proc-receive-refs.sh        | 128 +++++\n t/t5516-fetch-push.sh                         |   2 +-\n transport-helper.c                            |  53 +-\n transport.c                                   | 161 ++++--\n 49 files changed, 4307 insertions(+), 107 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/once-0010-report-status-v1.sh\n create mode 100644 t/t5411/test-0000-standard-git-push.sh\n create mode 100644 t/t5411/test-0001-standard-git-push--porcelain.sh\n create mode 100644 t/t5411/test-0002-pre-receive-declined.sh\n create mode 100644 t/t5411/test-0003-pre-receive-declined--porcelain.sh\n create mode 100644 t/t5411/test-0010-proc-receive-settings.sh\n create mode 100644 t/t5411/test-0011-no-hook-error.sh\n create mode 100644 t/t5411/test-0012-no-hook-error--porcelain.sh\n create mode 100644 t/t5411/test-0013-bad-protocol.sh\n create mode 100644 t/t5411/test-0014-bad-protocol--porcelain.sh\n create mode 100644 t/t5411/test-0020-report-ng.sh\n create mode 100644 t/t5411/test-0021-report-ng--porcelain.sh\n create mode 100644 t/t5411/test-0022-report-unexpect-ref.sh\n create mode 100644 t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n create mode 100644 t/t5411/test-0024-report-unknown-ref.sh\n create mode 100644 t/t5411/test-0025-report-unknown-ref--porcelain.sh\n create mode 100644 t/t5411/test-0026-push-options.sh\n create mode 100644 t/t5411/test-0027-push-options--porcelain.sh\n create mode 100644 t/t5411/test-0030-report-ok.sh\n create mode 100644 t/t5411/test-0031-report-ok--porcelain.sh\n create mode 100644 t/t5411/test-0032-report-with-options.sh\n create mode 100644 t/t5411/test-0033-report-with-options--porcelain.sh\n create mode 100644 t/t5411/test-0034-report-ft.sh\n create mode 100644 t/t5411/test-0035-report-ft--porcelain.sh\n create mode 100644 t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n create mode 100644 t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n create mode 100644 t/t5411/test-0038-report-mixed-refs.sh\n create mode 100644 t/t5411/test-0039-report-mixed-refs--porcelain.sh\n create mode 100644 t/t5411/test-0040-process-all-refs.sh\n create mode 100644 t/t5411/test-0041-process-all-refs--porcelain.sh\n create mode 100644 t/t5411/test-0050-proc-receive-refs.sh\n\n-- \n2.28.0\n\n"},{"id":"403884","messageId":"xmqq8sed0zau.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200815171740.6257-4-worldhello.net@gmail.com","subject":"Re: [PATCH v17 03/10] receive-pack: add new proc-receive hook","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-08-17T20:53:29Z","receivedAt":"2020-08-17T20:53:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n>\n> Suggested-by: Junio C Hamano <gitster@pobox.com>\n\nDid I????  I do not think I deserve any credit here.\n\n> Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\n\n> +static struct command *find_command_by_refname(const struct command *list,\n> +\t\t\t\t\t       const char *refname)\n> +{\n> +\tfor (; list; list = list->next)\n> +\t\tif (!strcmp(list->ref_name, refname))\n> +\t\t\treturn (struct command *)list;\n> +\treturn NULL;\n> +}\n\nDoes the last cast really needed, perhaps the caller is adding\n\"const\" to its objects where it is not needed, or the variable\ncaller uses to receive the returned value of too loose a type?\n\n\n> diff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\n> new file mode 100644\n> index 0000000000..4f352a7baf\n> --- /dev/null\n> +++ b/t/helper/test-proc-receive.c\n> @@ -0,0 +1,176 @@\n> +#include \"cache.h\"\n> +#include \"connect.h\"\n> +#include \"parse-options.h\"\n> +#include \"pkt-line.h\"\n> +#include \"sigchain.h\"\n> +#include \"test-tool.h\"\n> +\n> +static const char *proc_receive_usage[] = {\n> +\t\"test-tool proc-receive [<options>...]\",\n> +\tNULL\n> +};\n> +\n> +static int die_version = 0;\n> +static int die_readline = 0;\n> +static int no_push_options = 0;\n> +static int use_atomic = 0;\n> +static int use_push_options = 0;\n> +static int verbose = 0;\n\nLet BSS do its job by omitting \" = 0;\" and \" = NULL;\" at the end.\n\n"},{"id":"403885","messageId":"xmqq364l0yed.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200815171740.6257-5-worldhello.net@gmail.com","subject":"Re: [PATCH v17 04/10] New capability \"report-status-v2\" for git-push","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-08-17T21:12:58Z","receivedAt":"2020-08-17T21:13:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> +static void report_v2(struct command *commands, const char *unpack_status)\n> +{\n> +\tstruct command *cmd;\n> +\tstruct strbuf buf = STRBUF_INIT;\n> +\tstruct ref_push_report_options *options;\n> +\n> +\tpacket_buf_write(&buf, \"unpack %s\\n\",\n> +\t\t\t unpack_status ? unpack_status : \"ok\");\n> +\tfor (cmd = commands; cmd; cmd = cmd->next) {\n> +\t\tint count = 0;\n> +\n> +\t\tif (!cmd->report.error_message)\n> +\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n> +\t\t\t\t\t cmd->ref_name);\n> +\t\telse\n> +\t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n> +\t\t\t\t\t cmd->ref_name,\n> +\t\t\t\t\t cmd->report.error_message);\n> +\t\tfor (options = cmd->report.options; options; options = options->next) {\n> +\t\t\tif (count++ > 0)\n> +\t\t\t\tpacket_buf_write(&buf, \"ok %s\\n\", cmd->ref_name);\n\nIf there was an error, the first one gets \"ng\" but all others say it\nwas \"ok\"?  That smells somewhat strange.\n\n> +\t\t\tif (options->ref_name)\n> +\t\t\t\tpacket_buf_write(&buf, \"option refname %s\\n\",\n> +\t\t\t\t\t\t options->ref_name);\n> +\t\t\tif (options->old_oid)\n> +\t\t\t\tpacket_buf_write(&buf, \"option old-oid %s\\n\",\n> +\t\t\t\t\t\t oid_to_hex(options->old_oid));\n> +\t\t\tif (options->new_oid)\n> +\t\t\t\tpacket_buf_write(&buf, \"option new-oid %s\\n\",\n> +\t\t\t\t\t\t oid_to_hex(options->new_oid));\n> +\t\t\tif (options->forced_update)\n> +\t\t\t\tpacket_buf_write(&buf, \"option forced-update\\n\");\n> +\t\t}\n\n> +\t\tif (ref->report.error_message)\n> +\t\t\tmsg = ref->report.error_message;\n>  \t\tif (msg) {\n>  \t\t\tstrbuf_addch(&buf, ' ');\n>  \t\t\tquote_two_c_style(&buf, \"\", msg, 0);\n>  \t\t}\n>  \t\tstrbuf_addch(&buf, '\\n');\n>  \n> +\t\tfor (options = ref->report.options; options; options = options->next) {\n> +\t\t\tif (count++ > 0)\n> +\t\t\t\tstrbuf_addf(&buf, \"ok %s\\n\", ref->name);\n\nWould this one ever report a \"ng\" for an error?\n\n> +\t\t\tif (options->ref_name)\n> +\t\t\t\tstrbuf_addf(&buf, \"option refname %s\\n\",\n> +\t\t\t\t\t    options->ref_name);\n> +\t\t\tif (options->old_oid)\n> +\t\t\t\tstrbuf_addf(&buf, \"option old-oid %s\\n\",\n> +\t\t\t\t\t    oid_to_hex(options->old_oid));\n> +\t\t\tif (options->new_oid)\n> +\t\t\t\tstrbuf_addf(&buf, \"option new-oid %s\\n\",\n> +\t\t\t\t\t    oid_to_hex(options->new_oid));\n> +\t\t\tif (options->forced_update)\n> +\t\t\t\tstrbuf_addstr(&buf, \"option forced-update\\n\");\n> +\t\t}\n>  \t\twrite_or_die(1, buf.buf, buf.len);\n>  \t}\n>  \tstrbuf_release(&buf);\n\n> diff --git a/transport-helper.c b/transport-helper.c\n> index defafbf4c1..0029ba18bd 100644\n> --- a/transport-helper.c\n> +++ b/transport-helper.c\n> @@ -729,6 +729,49 @@ static int push_update_ref_status(struct strbuf *buf,\n>  {\n>  \tchar *refname, *msg;\n>  \tint status, forced = 0;\n> +\tstatic struct ref *hint = NULL;\n> +\tstatic int new_options = 1;\n\nI hate to see more helper functions turned into pieces that are\nimpossible to reuse by gaining internal state like this.  Does this\nfunction have so many callers that makes it impractical to update\nits signature to have the pointer to a \"state\" structure passed by\nthe caller?  This is called only from push_update_refs_status(), so\nyou should be able to define a new structure that has the two fields\n\n\tstruct {\n\t\tstruct ref *hint;\n\t\tint new_option;\n\t};\n\ninitialize an instance of it before push_update_refs_status() enters\nits endless loop, and pass a pointer to it to this helper function.\n\n> +\tif (starts_with(buf->buf, \"option \")) {\n> +\t\tstruct ref_push_report_options *options;\n> +\t\tstruct object_id old_oid, new_oid;\n> +\t\tconst char *key, *val;\n> +\t\tchar *p;\n> +\n> +\t\tif (!hint)\n> +\t\t\tdie(_(\"'option' without a matching 'ok/error' directive\"));\n> +\t\toptions = hint->report.options;\n> +\t\twhile (options && options->next)\n> +\t\t\toptions = options->next;\n> +\t\tif (new_options) {\n> +\t\t\tif (!options) {\n> +\t\t\t\thint->report.options = xcalloc(1, sizeof(struct ref_push_report_options));\n> +\t\t\t\toptions = hint->report.options;\n> +\t\t\t} else {\n> +\t\t\t\toptions->next = xcalloc(1, sizeof(struct ref_push_report_options));\n> +\t\t\t\toptions = options->next;\n> +\t\t\t}\n> +\t\t\tnew_options = 0;\n> +\t\t}\n\n\n> -static int print_one_push_status(struct ref *ref, const char *dest, int count,\n> -\t\t\t\t int porcelain, int summary_width)\n> +static int _print_one_push_status(struct ref *ref, const char *dest, int count,\n> +\t\t\t\t  struct ref_push_report_options *options,\n> +\t\t\t\t  int porcelain, int summary_width)\n\nOur naming convention avoids leading underscore (instead we use _1\nsuffix, when we cannot come up with a better name).\n\n> +static int print_one_push_status(struct ref *ref, const char *dest, int count,\n> +\t\t\t\t int porcelain, int summary_width)\n> +{\n> +\tstruct ref_push_report_options *options;\n> +\tint n = 0;\n> +\n> +\tif (!ref->report.options)\n> +\t\treturn _print_one_push_status(ref, dest, count,\n> +\t\t\t\t\t      NULL, porcelain, summary_width);\n> +\n> +\tfor (options = ref->report.options; options; options = options->next)\n\nThis comment applies to all the patches in this series that adds the\nsame pattern, but \n\n * \"ref->report.options\" may be a good name in that it is a (linked)\n   list of options;\n\n * The variable that is used to iterate over the list points at one\n   of the elements on the list at any time, and is better called\n   'option' without 's' at the end.\n\n> +\t\t_print_one_push_status(ref, dest, count + n++,\n> +\t\t\t\t       options, porcelain, summary_width);\n> +\treturn n;\n> +}\n> +\n>  static int measure_abbrev(const struct object_id *oid, int sofar)\n>  {\n>  \tchar hex[GIT_MAX_HEXSZ + 1];\n"},{"id":"403886","messageId":"xmqqy2mdynwv.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200815171740.6257-7-worldhello.net@gmail.com","subject":"Re: [PATCH v17 06/10] receive-pack: feed report options to post-receive","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-08-17T21:15:28Z","receivedAt":"2020-08-17T21:15:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\n> index 58972aa7cf..a687218167 100644\n> --- a/builtin/receive-pack.c\n> +++ b/builtin/receive-pack.c\n> @@ -777,17 +777,38 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n>  {\n>  \tstruct receive_hook_feed_state *state = state_;\n>  \tstruct command *cmd = state->cmd;\n> +\tstatic struct ref_push_report_options *options = NULL;\n\nExactly the same comment as \"don't turn helper functions that can be\nreused into unreusable ones by adding internal states\" as one of the\nprevious patches applies here.\n\n"},{"id":"403887","messageId":"xmqqtux1yngd.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200815171740.6257-8-worldhello.net@gmail.com","subject":"Re: [PATCH v17 07/10] receive-pack: new config receive.procReceiveRefs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-08-17T21:25:22Z","receivedAt":"2020-08-17T21:25:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\n> index a687218167..b7e4ee1483 100644\n> --- a/builtin/receive-pack.c\n> +++ b/builtin/receive-pack.c\n> @@ -98,6 +98,16 @@ static int keepalive_in_sec = 5;\n>  \n>  static struct tmp_objdir *tmp_objdir;\n>  \n> +static struct proc_receive_ref {\n> +\tunsigned int want_add:1,\n> +\t\t     want_delete:1,\n> +\t\t     want_modify:1;\n> +\tchar *ref_prefix;\n> +\tstruct proc_receive_ref *next;\n> +} *proc_receive_ref = NULL;\n\nLet BSS do its job here.\n\n> +static void proc_receive_ref_append(const char *prefix);\n> +\n>  static enum deny_action parse_deny_action(const char *var, const char *value)\n>  {\n>  \tif (value) {\n> @@ -230,6 +240,13 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n>  \t\treturn 0;\n>  \t}\n>  \n> +\tif (strcmp(var, \"receive.procreceiverefs\") == 0) {\n\nWe may want to fix the style of this function in a preliminary clean-up,\nbut not in the middle of the series, so let's agree to let this pass.\n\n> @@ -325,6 +342,84 @@ struct command {\n>  \tchar ref_name[FLEX_ARRAY]; /* more */\n>  };\n>  \n> +static void proc_receive_ref_append(const char *prefix)\n> +{\n> +\tstruct proc_receive_ref *ref_pattern;\n> +\tchar *p;\n> +\tint len;\n> +\n> +\tref_pattern = xcalloc(1, sizeof(struct proc_receive_ref));\n> +\tp = strchr(prefix, ':');\n\nA colon ':' is not a valid character in a refname, so the use of it\nas a separator for the prefix would be reasonable.\n\n> +\tif (p) {\n> +\t\twhile (prefix < p) {\n> +\t\t\tif (*prefix == 'a')\n> +\t\t\t\tref_pattern->want_add = 1;\n> +\t\t\telse if (*prefix == 'd')\n> +\t\t\t\tref_pattern->want_delete = 1;\n> +\t\t\telse if (*prefix == 'm')\n> +\t\t\t\tref_pattern->want_modify = 1;\n> +\t\t\tprefix++;\n> +\t\t}\n> +\t\tprefix++;\n> +\t} else {\n> +\t\tref_pattern->want_add = 1;\n> +\t\tref_pattern->want_delete = 1;\n> +\t\tref_pattern->want_modify = 1;\n> +\t}\n> +\tref_pattern->next = NULL;\n\n\n> +\tref_pattern->ref_prefix = xstrdup(prefix);\n> +\tlen = strlen(ref_pattern->ref_prefix);\n> +\twhile (len && ref_pattern->ref_prefix[len - 1] == '/')\n> +\t\tref_pattern->ref_prefix[--len] = '\\0';\n\nWhy not count \"size_t len\" before you make a copy of prefix with\ntail adjustment, so that you can do xmemdupz() at the end without\nneed for potential overallocation? \n\n> +\tif (proc_receive_ref == NULL) {\n\nStyle:\n\tif (!proc_receive_ref) {\n\nlike you did below.\n\n> +static int proc_receive_ref_matches(struct command *cmd)\n> +{\n> +\tstruct proc_receive_ref *p;\n> +\n> +\tif (!proc_receive_ref)\n> +\t\treturn 0;\n> +\n> +\tfor (p = proc_receive_ref; p; p = p->next) {\n> +\t\tconst char *match = p->ref_prefix;\n> +\t\tint neg = 0;\n> +\t\tconst char *remains;\n> +\n> +\t\tif (!p->want_add && is_null_oid(&cmd->old_oid))\n> +\t\t\tcontinue;\n> +\t\telse if (!p->want_delete && is_null_oid(&cmd->new_oid))\n> +\t\t\tcontinue;\n> +\t\telse if (!p->want_modify &&\n> +\t\t\t !is_null_oid(&cmd->old_oid) &&\n> +\t\t\t !is_null_oid(&cmd->new_oid))\n> +\t\t\tcontinue;\n> +\n> +\t\tif (*match == '!') {\n> +\t\t\tneg = 1;\n> +\t\t\tmatch++;\n> +\t\t}\n\nShouldn't the \"negate\" bit be jsut another field that has\nwant_{add,delete,modify}, so that proc_receive_ref_append()\ncan parse it only once without these four lines?\n\n"},{"id":"404332","messageId":"20200824174202.11710-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200815171740.6257-1-worldhello.net@gmail.com","subject":"[PATCH v18 00/10] New proc-receive hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-24T17:41:52Z","receivedAt":"2020-08-24T17:42:24Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\n## Changes since v17\n\nFixed all issues found in review on v17.\n\n> Did I????  I do not think I deserve any credit here.\n\n    @@ Commit message\n         extended in latter commit just like what the \"proc-receive\" hook reports\n         to \"receive-pack\".\n     \n    -    Suggested-by: Junio C Hamano <gitster@pobox.com>\n         Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\n\n> Does the last cast really needed, perhaps the caller is adding\n> \"const\" to its objects where it is not needed, or the variable\n> caller uses to receive the returned value of too loose a type?\n\n     @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n      \treturn finish_command(&proc);\n      }\n      \n    -+static struct command *find_command_by_refname(const struct command *list,\n    ++static struct command *find_command_by_refname(struct command *list,\n     +\t\t\t\t\t       const char *refname)\n     +{\n     +\tfor (; list; list = list->next)\n     +\t\tif (!strcmp(list->ref_name, refname))\n    -+\t\t\treturn (struct command *)list;\n    ++\t\t\treturn list;\n     +\treturn NULL;\n     +}\n     +\n\n> Let BSS do its job by omitting \" = 0;\" and \" = NULL;\" at the end.\n\n    @@ t/helper/test-proc-receive.c (new)\n     +\tNULL\n     +};\n     +\n    -+static int die_version = 0;\n    -+static int die_readline = 0;\n    -+static int no_push_options = 0;\n    -+static int use_atomic = 0;\n    -+static int use_push_options = 0;\n    -+static int verbose = 0;\n    ++static int die_version;\n    ++static int die_readline;\n    ++static int no_push_options;\n    ++static int use_atomic;\n    ++static int use_push_options;\n    ++static int verbose;\n     +static int version = 1;\n     +static struct string_list returns = STRING_LIST_INIT_NODUP;\n     +\n\n> This comment applies to all the patches in this series that adds the\n> same pattern, but \n> \n>  * \"ref->report.options\" may be a good name in that it is a (linked)\n>    list of options;\n> \n>  * The variable that is used to iterate over the list points at one\n>    of the elements on the list at any time, and is better called\n>    'option' without 's' at the end.\n\nMerge \"struct ref_push_report_options\" into \"struct ref_push_report\".\n\n     @@ remote.h: int for_each_remote(each_remote_fn fn, void *priv);\n      \n      int remote_has_url(struct remote *remote, const char *url);\n      \n    -+struct ref_push_report_options {\n    ++struct ref_push_report {\n     +\tconst char *ref_name;\n     +\tstruct object_id *old_oid;\n     +\tstruct object_id *new_oid;\n     +\tunsigned int forced_update:1;\n    -+\tstruct ref_push_report_options *next;\n    -+};\n    -+\n    -+struct ref_push_report {\n    -+\tconst char *message;\n    -+\tconst char *error_message;\n    -+\tstruct ref_push_report_options *options;\n    ++\tstruct ref_push_report *next;\n     +};\n     +\n      struct ref {\n\n> If there was an error, the first one gets \"ng\" but all others say it\n> was \"ok\"?  That smells somewhat strange.\n\n> Would this one ever report a \"ng\" for an error?\n\nPreserve \"error_string\" in command, and no \"options\" for an \"ng\" error.\n\n    @@ builtin/receive-pack.c: static void write_head_info(void)\n     +#define RUN_PROC_RECEIVE_RETURNED\t2\n      struct command {\n      \tstruct command *next;\n    --\tconst char *error_string;\n    -+\tstruct ref_push_report report;\n    + \tconst char *error_string;\n    ++\tstruct ref_push_report *report;\n      \tunsigned int skip_update:1,\n     -\t\t     did_not_exist:1;\n     +\t\t     did_not_exist:1,\n\n\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +{\n     +\tstruct command *cmd;\n     +\tstruct command *hint = NULL;\n    ++\tstruct ref_push_report *report = NULL;\n    ++\tint new_report = 0;\n     +\tint code = 0;\n    -+\tint new_options = 1;\n    ++\tint once = 0;\n     +\n     +\tfor (;;) {\n     +\t\tstruct object_id old_oid, new_oid;\n    @@ builtin/receive-pack.c: static int run_update_hook(struct command *cmd)\n     +\t\t}\n     +\t\t*p++ = '\\0';\n     +\t\tif (!strcmp(head, \"option\")) {\n    -+\t\t\tstruct ref_push_report_options *options;\n     +\t\t\tconst char *key, *val;\n     +\n    -+\t\t\tif (!hint) {\n    -+\t\t\t       if (new_options) {\n    ++\t\t\tif (!hint || !(report || new_report)) {\n    ++\t\t\t\tif (!once++)\n     +\t\t\t\t\tstrbuf_addstr(errmsg, \"proc-receive reported 'option' without a matching 'ok/ng' directive\\n\");\n    -+\t\t\t\t\tnew_options = 0;\n    -+\t\t\t\t}\n     +\t\t\t\tcode = -1;\n     +\t\t\t\tcontinue;\n     +\t\t\t}\n    -+\t\t\toptions = hint->report.options;\n    -+\t\t\twhile (options && options->next)\n    -+\t\t\t\toptions = options->next;\n    -+\t\t\tif (new_options) {\n    -+\t\t\t\tif (!options) {\n    -+\t\t\t\t\thint->report.options = xcalloc(1, sizeof(struct ref_push_report_options));\n    -+\t\t\t\t\toptions = hint->report.options;\n    ++\t\t\tif (new_report) {\n    ++\t\t\t\tif (!hint->report) {\n    ++\t\t\t\t\thint->report = xcalloc(1, sizeof(struct ref_push_report));\n    ++\t\t\t\t\treport = hint->report;\n     +\t\t\t\t} else {\n    -+\t\t\t\t\toptions->next = xcalloc(1, sizeof(struct ref_push_report_options));\n    -+\t\t\t\t\toptions = options->next;\n    ++\t\t\t\t\treport = hint->report;\n    ++\t\t\t\t\twhile (report->next)\n    ++\t\t\t\t\t\treport = report->next;\n    ++\t\t\t\t\treport->next = xcalloc(1, sizeof(struct ref_push_report));\n    ++\t\t\t\t\treport = report->next;\n     +\t\t\t\t}\n    -+\t\t\t\tnew_options = 0;\n    ++\t\t\t\tnew_report = 0;\n     +\t\t\t}\n    -+\t\t\tassert(options);\n     +\t\t\tkey = p;\n     +\t\t\tp = strchr(key, ' ');\n     +\t\t\tif (p)\n     +\t\t\t\t*p++ = '\\0';\n     +\t\t\tval = p;\n     +\t\t\tif (!strcmp(key, \"refname\"))\n    -+\t\t\t\toptions->ref_name = xstrdup_or_null(val);\n    ++\t\t\t\treport->ref_name = xstrdup_or_null(val);\n     +\t\t\telse if (!strcmp(key, \"old-oid\") && val &&\n     +\t\t\t\t !parse_oid_hex(val, &old_oid, &val))\n    -+\t\t\t\toptions->old_oid = oiddup(&old_oid);\n    ++\t\t\t\treport->old_oid = oiddup(&old_oid);\n     +\t\t\telse if (!strcmp(key, \"new-oid\") && val &&\n     +\t\t\t\t !parse_oid_hex(val, &new_oid, &val))\n    -+\t\t\t\toptions->new_oid = oiddup(&new_oid);\n    ++\t\t\t\treport->new_oid = oiddup(&new_oid);\n     +\t\t\telse if (!strcmp(key, \"forced-update\"))\n    -+\t\t\t\toptions->forced_update = 1;\n    ++\t\t\t\treport->forced_update = 1;\n     +\t\t\telse if (!strcmp(key, \"fall-through\"))\n     +\t\t\t\t/* Fall through, let 'receive-pack' to execute it. */\n     +\t\t\t\thint->run_proc_receive = 0;\n     +\t\t\tcontinue;\n     +\t\t}\n     +\n    ++\t\treport = NULL;\n    ++\t\tnew_report = 0;\n     +\t\trefname = p;\n     +\t\tp = strchr(refname, ' ');\n     +\t\tif (p)\n\n> Our naming convention avoids leading underscore (instead we use _1\n> suffix, when we cannot come up with a better name).\n\nRename \"_print_one_push_status\" to \"print_one_push_report\".\n\n    @@ transport.c: static void print_ok_ref_status(struct ref *ref, int porcelain, int\n      \n      \t\tprint_ref_status(type, quickref.buf, ref, ref->peer_ref, msg,\n     -\t\t\t\t porcelain, summary_width);\n    -+\t\t\t\t options, porcelain, summary_width);\n    ++\t\t\t\t report, porcelain, summary_width);\n      \t\tstrbuf_release(&quickref);\n      \t}\n      }\n      \n     -static int print_one_push_status(struct ref *ref, const char *dest, int count,\n    --\t\t\t\t int porcelain, int summary_width)\n    -+static int _print_one_push_status(struct ref *ref, const char *dest, int count,\n    -+\t\t\t\t  struct ref_push_report_options *options,\n    -+\t\t\t\t  int porcelain, int summary_width)\n    ++static int print_one_push_report(struct ref *ref, const char *dest, int count,\n    ++\t\t\t\t struct ref_push_report *report,\n    + \t\t\t\t int porcelain, int summary_width)\n      {\n      \tif (!count) {\n    - \t\tchar *url = transport_anonymize_url(dest);\n\n\n> I hate to see more helper functions turned into pieces that are\n> impossible to reuse by gaining internal state like this.  Does this\n> function have so many callers that makes it impractical to update\n> its signature to have the pointer to a \"state\" structure passed by\n> the caller?  This is called only from push_update_refs_status(), so\n> you should be able to define a new structure that has the two fields\n> \n> \tstruct {\n> \t\tstruct ref *hint;\n> \t\tint new_option;\n> \t};\n> \n> initialize an instance of it before push_update_refs_status() enters\n> its endless loop, and pass a pointer to it to this helper function.\n\n      ## transport-helper.c ##\n    -@@ transport-helper.c: static int push_update_ref_status(struct strbuf *buf,\n    +@@ transport-helper.c: static int fetch(struct transport *transport,\n    + \treturn -1;\n    + }\n    + \n    ++struct push_update_ref_state {\n    ++\tstruct ref *hint;\n    ++\tstruct ref_push_report *report;\n    ++\tint new_report;\n    ++};\n    ++\n    + static int push_update_ref_status(struct strbuf *buf,\n    +-\t\t\t\t   struct ref **ref,\n    ++\t\t\t\t   struct push_update_ref_state *state,\n    + \t\t\t\t   struct ref *remote_refs)\n      {\n      \tchar *refname, *msg;\n      \tint status, forced = 0;\n    -+\tstatic struct ref *hint = NULL;\n    -+\tstatic int new_options = 1;\n    -+\n    + \n     +\tif (starts_with(buf->buf, \"option \")) {\n    -+\t\tstruct ref_push_report_options *options;\n     +\t\tstruct object_id old_oid, new_oid;\n     +\t\tconst char *key, *val;\n     +\t\tchar *p;\n     +\n    -+\t\tif (!hint)\n    ++\t\tif (!state->hint || !(state->report || state->new_report))\n     +\t\t\tdie(_(\"'option' without a matching 'ok/error' directive\"));\n    -+\t\toptions = hint->report.options;\n    -+\t\twhile (options && options->next)\n    -+\t\t\toptions = options->next;\n    -+\t\tif (new_options) {\n    -+\t\t\tif (!options) {\n    -+\t\t\t\thint->report.options = xcalloc(1, sizeof(struct ref_push_report_options));\n    -+\t\t\t\toptions = hint->report.options;\n    ++\t\tif (state->new_report) {\n    ++\t\t\tif (!state->hint->report) {\n    ++\t\t\t\tstate->hint->report = xcalloc(1, sizeof(struct ref_push_report));\n    ++\t\t\t\tstate->report = state->hint->report;\n     +\t\t\t} else {\n    -+\t\t\t\toptions->next = xcalloc(1, sizeof(struct ref_push_report_options));\n    -+\t\t\t\toptions = options->next;\n    ++\t\t\t\tstate->report = state->hint->report;\n    ++\t\t\t\twhile (state->report->next)\n    ++\t\t\t\t\tstate->report = state->report->next;\n    ++\t\t\t\tstate->report->next = xcalloc(1, sizeof(struct ref_push_report));\n    ++\t\t\t\tstate->report = state->report->next;\n     +\t\t\t}\n    -+\t\t\tnew_options = 0;\n    ++\t\t\tstate->new_report = 0;\n     +\t\t}\n    -+\t\tassert(options);\n     +\t\tkey = buf->buf + 7;\n     +\t\tp = strchr(key, ' ');\n     +\t\tif (p)\n     +\t\t\t*p++ = '\\0';\n     +\t\tval = p;\n     +\t\tif (!strcmp(key, \"refname\"))\n    -+\t\t\toptions->ref_name = xstrdup_or_null(val);\n    ++\t\t\tstate->report->ref_name = xstrdup_or_null(val);\n     +\t\telse if (!strcmp(key, \"old-oid\") && val &&\n     +\t\t\t !parse_oid_hex(val, &old_oid, &val))\n    -+\t\t\toptions->old_oid = oiddup(&old_oid);\n    ++\t\t\tstate->report->old_oid = oiddup(&old_oid);\n     +\t\telse if (!strcmp(key, \"new-oid\") && val &&\n     +\t\t\t !parse_oid_hex(val, &new_oid, &val))\n    -+\t\t\toptions->new_oid = oiddup(&new_oid);\n    ++\t\t\tstate->report->new_oid = oiddup(&new_oid);\n     +\t\telse if (!strcmp(key, \"forced-update\"))\n    -+\t\t\toptions->forced_update = 1;\n    ++\t\t\tstate->report->forced_update = 1;\n     +\t\t/* Not update remote namespace again. */\n     +\t\treturn 1;\n     +\t}\n    - \n    ++\n    ++\tstate->report = NULL;\n    ++\tstate->new_report = 0;\n    ++\n      \tif (starts_with(buf->buf, \"ok \")) {\n      \t\tstatus = REF_STATUS_OK;\n    + \t\trefname = buf->buf + 3;\n     @@ transport-helper.c: static int push_update_ref_status(struct strbuf *buf,\n    - \t\t*ref = find_ref_by_name(remote_refs, refname);\n    - \tif (!*ref) {\n    + \t\t}\n    + \t}\n    + \n    +-\tif (*ref)\n    +-\t\t*ref = find_ref_by_name(*ref, refname);\n    +-\tif (!*ref)\n    +-\t\t*ref = find_ref_by_name(remote_refs, refname);\n    +-\tif (!*ref) {\n    ++\tif (state->hint)\n    ++\t\tstate->hint = find_ref_by_name(state->hint, refname);\n    ++\tif (!state->hint)\n    ++\t\tstate->hint = find_ref_by_name(remote_refs, refname);\n    ++\tif (!state->hint) {\n      \t\twarning(_(\"helper reported unexpected status of %s\"), refname);\n    -+\t\thint = NULL;\n      \t\treturn 1;\n      \t}\n    -+\thint = *ref;\n    -+\tnew_options = 1;\n      \n    - \tif ((*ref)->status != REF_STATUS_NONE) {\n    +-\tif ((*ref)->status != REF_STATUS_NONE) {\n    ++\tif (state->hint->status != REF_STATUS_NONE) {\n      \t\t/*\n    + \t\t * Earlier, the ref was marked not to be pushed, so ignore the ref\n    + \t\t * status reported by the remote helper if the latter is 'no match'.\n     @@ transport-helper.c: static int push_update_ref_status(struct strbuf *buf,\n    + \t\t\treturn 1;\n    + \t}\n      \n    - \t(*ref)->status = status;\n    - \t(*ref)->forced_update |= forced;\n    +-\t(*ref)->status = status;\n    +-\t(*ref)->forced_update |= forced;\n     -\t(*ref)->remote_status = msg;\n    -+\tif (msg) {\n    -+\t\tif (status == REF_STATUS_OK)\n    -+\t\t\t(*ref)->report.message = msg;\n    -+\t\telse\n    -+\t\t\t(*ref)->report.error_message = msg;\n    -+\t}\n    ++\tif (status == REF_STATUS_OK)\n    ++\t\tstate->new_report = 1;\n    ++\tstate->hint->status = status;\n    ++\tstate->hint->forced_update |= forced;\n    ++\tstate->hint->remote_status = msg;\n      \treturn !(status == REF_STATUS_OK);\n      }\n      \n    +@@ transport-helper.c: static int push_update_refs_status(struct helper_data *data,\n    + \t\t\t\t    struct ref *remote_refs,\n    + \t\t\t\t    int flags)\n    + {\n    ++\tstruct ref *ref;\n    ++\tstruct ref_push_report *report;\n    + \tstruct strbuf buf = STRBUF_INIT;\n    +-\tstruct ref *ref = remote_refs;\n    +-\tint ret = 0;\n    ++\tstruct push_update_ref_state state = { remote_refs, NULL, 0 };\n    + \n    + \tfor (;;) {\n    +-\t\tchar *private;\n    +-\n    + \t\tif (recvline(data, &buf)) {\n    +-\t\t\tret = 1;\n    +-\t\t\tbreak;\n    ++\t\t\tstrbuf_release(&buf);\n    ++\t\t\treturn 1;\n    + \t\t}\n    +-\n    + \t\tif (!buf.len)\n    + \t\t\tbreak;\n    ++\t\tpush_update_ref_status(&buf, &state, remote_refs);\n    ++\t}\n    ++\tstrbuf_release(&buf);\n    + \n    +-\t\tif (push_update_ref_status(&buf, &ref, remote_refs))\n    +-\t\t\tcontinue;\n    ++\tif (flags & TRANSPORT_PUSH_DRY_RUN || !data->rs.nr || data->no_private_update)\n    ++\t\treturn 0;\n    + \n    +-\t\tif (flags & TRANSPORT_PUSH_DRY_RUN || !data->rs.nr || data->no_private_update)\n    +-\t\t\tcontinue;\n    ++\t/* propagate back the update to the remote namespace */\n    ++\tfor (ref = remote_refs; ref; ref = ref->next) {\n    ++\t\tchar *private;\n    + \n    +-\t\t/* propagate back the update to the remote namespace */\n    +-\t\tprivate = apply_refspecs(&data->rs, ref->name);\n    +-\t\tif (!private)\n    ++\t\tif (ref->status != REF_STATUS_OK)\n    + \t\t\tcontinue;\n    +-\t\tupdate_ref(\"update by helper\", private, &ref->new_oid, NULL,\n    +-\t\t\t   0, 0);\n    +-\t\tfree(private);\n    ++\n    ++\t\tif (!ref->report) {\n    ++\t\t\tprivate = apply_refspecs(&data->rs, ref->name);\n    ++\t\t\tif (!private)\n    ++\t\t\t\tcontinue;\n    ++\t\t\tupdate_ref(\"update by helper\", private, &(ref->new_oid),\n    ++\t\t\t\t   NULL, 0, 0);\n    ++\t\t\tfree(private);\n    ++\t\t} else {\n    ++\t\t\tfor (report = ref->report; report; report = report->next) {\n    ++\t\t\t\tprivate = apply_refspecs(&data->rs,\n    ++\t\t\t\t\t\t\t report->ref_name\n    ++\t\t\t\t\t\t\t ? report->ref_name\n    ++\t\t\t\t\t\t\t : ref->name);\n    ++\t\t\t\tif (!private)\n    ++\t\t\t\t\tcontinue;\n    ++\t\t\t\tupdate_ref(\"update by helper\", private,\n    ++\t\t\t\t\t   report->new_oid\n    ++\t\t\t\t\t   ? report->new_oid\n    ++\t\t\t\t\t   : &(ref->new_oid),\n    ++\t\t\t\t\t   NULL, 0, 0);\n    ++\t\t\t\tfree(private);\n    ++\t\t\t}\n    ++\t\t}\n    + \t}\n    +-\tstrbuf_release(&buf);\n    +-\treturn ret;\n    ++\treturn 0;\n    + }\n    + \n    + static void set_common_push_options(struct transport *transport,\n\n> Exactly the same comment as \"don't turn helper functions that can be\n> reused into unreusable ones by adding internal states\" as one of the\n> previous patches applies here.\n\n\n 6:  ca4fc6d511 !  4:  60b9157a8e receive-pack: feed report options to post-receive\n    @@ Commit message\n         Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n     \n      ## builtin/receive-pack.c ##\n    -@@ builtin/receive-pack.c: static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n    - {\n    - \tstruct receive_hook_feed_state *state = state_;\n    - \tstruct command *cmd = state->cmd;\n    -+\tstatic struct ref_push_report_options *options = NULL;\n    +@@ builtin/receive-pack.c: static void prepare_push_cert_sha1(struct child_process *proc)\n      \n    - \twhile (cmd &&\n    - \t       state->skip_broken && (cmd->report.error_message || cmd->did_not_exist))\n    + struct receive_hook_feed_state {\n    + \tstruct command *cmd;\n    ++\tstruct ref_push_report *report;\n    + \tint skip_broken;\n    + \tstruct strbuf buf;\n    + \tconst struct string_list *push_options;\n    +@@ builtin/receive-pack.c: static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n      \t\tcmd = cmd->next;\n      \tif (!cmd)\n      \t\treturn -1; /* EOF */\n    @@ builtin/receive-pack.c: static int feed_receive_hook(void *state_, const char **\n     -\t\t    oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),\n     -\t\t    cmd->ref_name);\n     -\tstate->cmd = cmd->next;\n    -+\tif (!options)\n    -+\t\toptions = cmd->report.options;\n    -+\tif (options) {\n    ++\tif (!state->report)\n    ++\t\tstate->report = cmd->report;\n    ++\tif (state->report) {\n     +\t\tstruct object_id *old_oid;\n     +\t\tstruct object_id *new_oid;\n     +\t\tconst char *ref_name;\n     +\n    -+\t\told_oid = options->old_oid ? options->old_oid : &cmd->old_oid;\n    -+\t\tnew_oid = options->new_oid ? options->new_oid : &cmd->new_oid;\n    -+\t\tref_name = options->ref_name ? options->ref_name : cmd->ref_name;\n    ++\t\told_oid = state->report->old_oid ? state->report->old_oid : &cmd->old_oid;\n    ++\t\tnew_oid = state->report->new_oid ? state->report->new_oid : &cmd->new_oid;\n    ++\t\tref_name = state->report->ref_name ? state->report->ref_name : cmd->ref_name;\n     +\t\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n     +\t\t\t    oid_to_hex(old_oid), oid_to_hex(new_oid),\n     +\t\t\t    ref_name);\n    -+\t\toptions = options->next;\n    -+\t\tif (!options)\n    ++\t\tstate->report = state->report->next;\n    ++\t\tif (!state->report)\n     +\t\t\tstate->cmd = cmd->next;\n     +\t} else {\n     +\t\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n\n    +@@ builtin/receive-pack.c: static int run_receive_hook(struct command *commands,\n    + \tstrbuf_init(&state.buf, 0);\n    + \tstate.cmd = commands;\n    + \tstate.skip_broken = skip_broken;\n    ++\tstate.report = NULL;\n    + \tif (feed_receive_hook(&state, NULL, NULL))\n    + \t\treturn 0;\n    + \tstate.cmd = commands;\n \n> Why not count \"size_t len\" before you make a copy of prefix with\n> tail adjustment, so that you can do xmemdupz() at the end without\n> need for potential overallocation? \n\n> Style:\n> \tif (!proc_receive_ref) {\n> \n> like you did below.\n\n    @@ builtin/receive-pack.c: struct command {\n     +\t\tref_pattern->want_delete = 1;\n     +\t\tref_pattern->want_modify = 1;\n     +\t}\n    -+\tref_pattern->next = NULL;\n    -+\tref_pattern->ref_prefix = xstrdup(prefix);\n    -+\tlen = strlen(ref_pattern->ref_prefix);\n    -+\twhile (len && ref_pattern->ref_prefix[len - 1] == '/')\n    -+\t\tref_pattern->ref_prefix[--len] = '\\0';\n    -+\tif (proc_receive_ref == NULL) {\n    ++\tlen = strlen(prefix);\n    ++\twhile (len && prefix[len - 1] == '/')\n    ++\t\tlen--;\n    ++\tref_pattern->ref_prefix = xmemdupz(prefix, len);\n    ++\tif (!proc_receive_ref) {\n     +\t\tproc_receive_ref = ref_pattern;\n     +\t} else {\n     +\t\tstruct proc_receive_ref *end;\n\n\n> Shouldn't the \"negate\" bit be jsut another field that has\n> want_{add,delete,modify}, so that proc_receive_ref_append()\n> can parse it only once without these four lines?\n\n    @@ builtin/receive-pack.c: static int keepalive_in_sec = 5;\n     +static struct proc_receive_ref {\n     +\tunsigned int want_add:1,\n     +\t\t     want_delete:1,\n    -+\t\t     want_modify:1;\n    ++\t\t     want_modify:1,\n    ++\t\t     negative_ref:1;\n     +\tchar *ref_prefix;\n     +\tstruct proc_receive_ref *next;\n    -+} *proc_receive_ref = NULL;\n    ++} *proc_receive_ref;\n     +\n     +static void proc_receive_ref_append(const char *prefix);\n     +\n    @@ builtin/receive-pack.c: struct command {\n     +\t\t\t\tref_pattern->want_delete = 1;\n     +\t\t\telse if (*prefix == 'm')\n     +\t\t\t\tref_pattern->want_modify = 1;\n    ++\t\t\telse if (*prefix == '!')\n    ++\t\t\t\tref_pattern->negative_ref = 1;\n     +\t\t\tprefix++;\n     +\t\t}\n     +\t\tprefix++;\n\n    @@ builtin/receive-pack.c: struct command {\n     +\n     +\tfor (p = proc_receive_ref; p; p = p->next) {\n     +\t\tconst char *match = p->ref_prefix;\n    -+\t\tint neg = 0;\n     +\t\tconst char *remains;\n     +\n     +\t\tif (!p->want_add && is_null_oid(&cmd->old_oid))\n    @@ builtin/receive-pack.c: struct command {\n     +\t\t\t !is_null_oid(&cmd->new_oid))\n     +\t\t\tcontinue;\n     +\n    -+\t\tif (*match == '!') {\n    -+\t\t\tneg = 1;\n    -+\t\t\tmatch++;\n    -+\t\t}\n    -+\n     +\t\tif (skip_prefix(cmd->ref_name, match, &remains) &&\n     +\t\t    (!*remains || *remains == '/')) {\n    -+\t\t\tif (!neg)\n    ++\t\t\tif (!p->negative_ref)\n     +\t\t\t\treturn 1;\n    -+\t\t} else if (neg) {\n    ++\t\t} else if (p->negative_ref) {\n     +\t\t\treturn 1;\n     +\t\t}\n     +\t}\n    @@ t/t5411/test-0050-proc-receive-refs.sh (new)\n     +\t\tgit config --unset-all receive.procReceiveRefs &&\n     +\t\tgit config --add receive.procReceiveRefs m:refs/heads/master &&\n     +\t\tgit config --add receive.procReceiveRefs ad:refs/heads &&\n    -+\t\tgit config --add receive.procReceiveRefs \"a:!refs/heads\"\n    ++\t\tgit config --add receive.procReceiveRefs \"a!:refs/heads\"\n     +\t)\n     +'\n     +\n\n---\n\nJiang Xin (10):\n  transport: not report a non-head push as a branch\n  t5411: add basic test cases for proc-receive hook\n  receive-pack: add new proc-receive hook\n  receive-pack: feed report options to post-receive\n  New capability \"report-status-v2\" for git-push\n  doc: add document for capability report-status-v2\n  receive-pack: new config receive.procReceiveRefs\n  t5411: test updates of remote-tracking branches\n  transport: parse report options for tracking refs\n  doc: add documentation for the proc-receive hook\n\n Documentation/config/receive.txt              |  21 +\n Documentation/githooks.txt                    |  62 +++\n Documentation/technical/pack-protocol.txt     |  43 +-\n .../technical/protocol-capabilities.txt       |  17 +-\n Makefile                                      |   1 +\n builtin/receive-pack.c                        | 472 +++++++++++++++++-\n builtin/send-pack.c                           |  19 +\n remote.h                                      |   9 +\n send-pack.c                                   | 101 +++-\n t/helper/test-proc-receive.c                  | 176 +++++++\n t/helper/test-tool.c                          |   1 +\n t/helper/test-tool.h                          |   1 +\n t/t5411-proc-receive-hook.sh                  | 117 +++++\n t/t5411/common-functions.sh                   |  56 +++\n t/t5411/once-0010-report-status-v1.sh         |  94 ++++\n t/t5411/test-0000-standard-git-push.sh        | 143 ++++++\n .../test-0001-standard-git-push--porcelain.sh | 147 ++++++\n t/t5411/test-0002-pre-receive-declined.sh     |  33 ++\n ...st-0003-pre-receive-declined--porcelain.sh |  34 ++\n t/t5411/test-0010-proc-receive-settings.sh    |   7 +\n t/t5411/test-0011-no-hook-error.sh            |  64 +++\n t/t5411/test-0012-no-hook-error--porcelain.sh |  66 +++\n t/t5411/test-0013-bad-protocol.sh             | 217 ++++++++\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 160 ++++++\n t/t5411/test-0020-report-ng.sh                |  67 +++\n t/t5411/test-0021-report-ng--porcelain.sh     |  69 +++\n t/t5411/test-0022-report-unexpect-ref.sh      |  45 ++\n ...est-0023-report-unexpect-ref--porcelain.sh |  46 ++\n t/t5411/test-0024-report-unknown-ref.sh       |  34 ++\n ...test-0025-report-unknown-ref--porcelain.sh |  35 ++\n t/t5411/test-0026-push-options.sh             |  79 +++\n t/t5411/test-0027-push-options--porcelain.sh  |  82 +++\n t/t5411/test-0030-report-ok.sh                |  35 ++\n t/t5411/test-0031-report-ok--porcelain.sh     |  36 ++\n t/t5411/test-0032-report-with-options.sh      | 256 ++++++++++\n ...est-0033-report-with-options--porcelain.sh | 265 ++++++++++\n t/t5411/test-0034-report-ft.sh                |  44 ++\n t/t5411/test-0035-report-ft--porcelain.sh     |  45 ++\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 227 +++++++++\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 172 +++++++\n t/t5411/test-0038-report-mixed-refs.sh        |  89 ++++\n .../test-0039-report-mixed-refs--porcelain.sh |  91 ++++\n t/t5411/test-0040-process-all-refs.sh         | 113 +++++\n .../test-0041-process-all-refs--porcelain.sh  | 114 +++++\n t/t5411/test-0050-proc-receive-refs.sh        | 128 +++++\n t/t5516-fetch-push.sh                         |   2 +-\n transport-helper.c                            | 128 +++--\n transport.c                                   | 159 ++++--\n 48 files changed, 4320 insertions(+), 102 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/once-0010-report-status-v1.sh\n create mode 100644 t/t5411/test-0000-standard-git-push.sh\n create mode 100644 t/t5411/test-0001-standard-git-push--porcelain.sh\n create mode 100644 t/t5411/test-0002-pre-receive-declined.sh\n create mode 100644 t/t5411/test-0003-pre-receive-declined--porcelain.sh\n create mode 100644 t/t5411/test-0010-proc-receive-settings.sh\n create mode 100644 t/t5411/test-0011-no-hook-error.sh\n create mode 100644 t/t5411/test-0012-no-hook-error--porcelain.sh\n create mode 100644 t/t5411/test-0013-bad-protocol.sh\n create mode 100644 t/t5411/test-0014-bad-protocol--porcelain.sh\n create mode 100644 t/t5411/test-0020-report-ng.sh\n create mode 100644 t/t5411/test-0021-report-ng--porcelain.sh\n create mode 100644 t/t5411/test-0022-report-unexpect-ref.sh\n create mode 100644 t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n create mode 100644 t/t5411/test-0024-report-unknown-ref.sh\n create mode 100644 t/t5411/test-0025-report-unknown-ref--porcelain.sh\n create mode 100644 t/t5411/test-0026-push-options.sh\n create mode 100644 t/t5411/test-0027-push-options--porcelain.sh\n create mode 100644 t/t5411/test-0030-report-ok.sh\n create mode 100644 t/t5411/test-0031-report-ok--porcelain.sh\n create mode 100644 t/t5411/test-0032-report-with-options.sh\n create mode 100644 t/t5411/test-0033-report-with-options--porcelain.sh\n create mode 100644 t/t5411/test-0034-report-ft.sh\n create mode 100644 t/t5411/test-0035-report-ft--porcelain.sh\n create mode 100644 t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n create mode 100644 t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n create mode 100644 t/t5411/test-0038-report-mixed-refs.sh\n create mode 100644 t/t5411/test-0039-report-mixed-refs--porcelain.sh\n create mode 100644 t/t5411/test-0040-process-all-refs.sh\n create mode 100644 t/t5411/test-0041-process-all-refs--porcelain.sh\n create mode 100644 t/t5411/test-0050-proc-receive-refs.sh\n\n-- \n2.26.2.543.ge7de2a6e37\n\n"},{"id":"404333","messageId":"20200824174202.11710-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200815171740.6257-1-worldhello.net@gmail.com","subject":"[PATCH v18 01/10] transport: not report a non-head push as a branch","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-24T17:41:53Z","receivedAt":"2020-08-24T17:42:26Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing a new reference (not a head or tag), report it as a new\nreference instead of a new branch.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5516-fetch-push.sh | 2 +-\n transport.c           | 9 ++++++---\n 2 files changed, 7 insertions(+), 4 deletions(-)\n\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 9c6218f568..77938db77f 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1039,7 +1039,7 @@ test_force_fetch_tag \"annotated tag\" \"-f -a -m'tag message'\"\n test_expect_success 'push --porcelain' '\n \tmk_empty testrepo &&\n \techo >.git/foo  \"To testrepo\" &&\n-\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new branch]\"  &&\n+\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new reference]\"  &&\n \techo >>.git/foo \"Done\" &&\n \tgit push >.git/bar --porcelain  testrepo refs/heads/master:refs/remotes/origin/master &&\n \t(\ndiff --git a/transport.c b/transport.c\nindex 15f5ba4e8f..26fefd13c1 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -500,9 +500,12 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\t\t\t porcelain, summary_width);\n \telse if (is_null_oid(&ref->old_oid))\n \t\tprint_ref_status('*',\n-\t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n-\t\t\t\"[new branch]\"),\n-\t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t  ? \"[new tag]\"\n+\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t     ? \"[new branch]\"\n+\t\t\t\t     : \"[new reference]\")),\n+\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n-- \n2.26.2.543.ge7de2a6e37\n\n"},{"id":"404334","messageId":"20200824174202.11710-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200815171740.6257-1-worldhello.net@gmail.com","subject":"[PATCH v18 02/10] t5411: add basic test cases for proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-24T17:41:54Z","receivedAt":"2020-08-24T17:42:31Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nTopic \"proc-receive-hook\" will change the workflow and output of\ngit-push. Add some basic test cases in t5411 before introducing the new\ntopic.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411-proc-receive-hook.sh                  | 108 +++++++++++++\n t/t5411/common-functions.sh                   |  56 +++++++\n t/t5411/test-0000-standard-git-push.sh        | 143 +++++++++++++++++\n .../test-0001-standard-git-push--porcelain.sh | 147 ++++++++++++++++++\n t/t5411/test-0002-pre-receive-declined.sh     |  33 ++++\n ...st-0003-pre-receive-declined--porcelain.sh |  34 ++++\n 6 files changed, 521 insertions(+)\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/test-0000-standard-git-push.sh\n create mode 100644 t/t5411/test-0001-standard-git-push--porcelain.sh\n create mode 100644 t/t5411/test-0002-pre-receive-declined.sh\n create mode 100644 t/t5411/test-0003-pre-receive-declined--porcelain.sh\n\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nnew file mode 100755\nindex 0000000000..3a684353a8\n--- /dev/null\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -0,0 +1,108 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook'\n+\n+. ./test-lib.sh\n+\n+. \"$TEST_DIRECTORY\"/t5411/common-functions.sh\n+\n+setup_upstream_and_workbench () {\n+\t# Refs of upstream : master(A)\n+\t# Refs of workbench: master(A)  tags/v123\n+\ttest_expect_success \"setup upstream and workbench\" '\n+\t\trm -rf upstream.git &&\n+\t\trm -rf workbench &&\n+\t\tgit init --bare upstream.git &&\n+\t\tgit init workbench &&\n+\t\tcreate_commits_in workbench A B &&\n+\t\t(\n+\t\t\tcd workbench &&\n+\t\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\t\tgit config core.abbrev 7 &&\n+\t\t\tgit tag -m \"v123\" v123 $A &&\n+\t\t\tgit remote add origin ../upstream.git &&\n+\t\t\tgit push origin master &&\n+\t\t\tgit update-ref refs/heads/master $A $B &&\n+\t\t\tgit -C ../upstream.git update-ref \\\n+\t\t\t\trefs/heads/master $A $B\n+\t\t) &&\n+\t\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t\t# setup pre-receive hook\n+\t\twrite_script upstream.git/hooks/pre-receive <<-\\EOF &&\n+\t\texec >&2\n+\t\techo \"# pre-receive hook\"\n+\t\twhile read old new ref\n+\t\tdo\n+\t\t\techo \"pre-receive< $old $new $ref\"\n+\t\tdone\n+\t\tEOF\n+\n+\t\t# setup post-receive hook\n+\t\twrite_script upstream.git/hooks/post-receive <<-\\EOF &&\n+\t\texec >&2\n+\t\techo \"# post-receive hook\"\n+\t\twhile read old new ref\n+\t\tdo\n+\t\t\techo \"post-receive< $old $new $ref\"\n+\t\tdone\n+\t\tEOF\n+\n+\t\tupstream=upstream.git\n+\t'\n+}\n+\n+run_proc_receive_hook_test() {\n+\tcase $1 in\n+\thttp)\n+\t\tPROTOCOL=\"HTTP protocol\"\n+\t\tURL_PREFIX=\"http://.*\"\n+\t\t;;\n+\tlocal)\n+\t\tPROTOCOL=\"builtin protocol\"\n+\t\tURL_PREFIX=\"\\.\\.\"\n+\t\t;;\n+\tesac\n+\n+\t# Include test cases for both file and HTTP protocol\n+\tfor t in  \"$TEST_DIRECTORY\"/t5411/test-*.sh\n+\tdo\n+\t\t. \"$t\"\n+\tdone\n+}\n+\n+# Initialize the upstream repository and local workbench.\n+setup_upstream_and_workbench\n+\n+# Run test cases for 'proc-receive' hook on local file protocol.\n+run_proc_receive_hook_test local\n+\n+ROOT_PATH=\"$PWD\"\n+. \"$TEST_DIRECTORY\"/lib-gpg.sh\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+. \"$TEST_DIRECTORY\"/lib-terminal.sh\n+start_httpd\n+\n+# Re-initialize the upstream repository and local workbench.\n+setup_upstream_and_workbench\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup for HTTP protocol\" '\n+\tgit -C upstream.git config http.receivepack true &&\n+\tupstream=\"$HTTPD_DOCUMENT_ROOT_PATH/upstream.git\" &&\n+\tmv upstream.git \"$upstream\" &&\n+\tgit -C workbench remote set-url origin \"$HTTPD_URL/auth-push/smart/upstream.git\" &&\n+\tset_askpass user@host pass@host\n+'\n+\n+setup_askpass_helper\n+\n+# Run test cases for 'proc-receive' hook on HTTP protocol.\n+run_proc_receive_hook_test http\n+\n+test_done\ndiff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\nnew file mode 100644\nindex 0000000000..6580bebd8e\n--- /dev/null\n+++ b/t/t5411/common-functions.sh\n@@ -0,0 +1,56 @@\n+# Create commits in <repo> and assign each commit's oid to shell variables\n+# given in the arguments (A, B, and C). E.g.:\n+#\n+#     create_commits_in <repo> A B C\n+#\n+# NOTE: Never calling this function from a subshell since variable\n+# assignments will disappear when subshell exits.\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{} --)\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  We can easily prepare the expect text\n+# without having to worry about future changes of the commit ID and spaces\n+# of the output.  Single quotes are replaced with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect text.  We also\n+# remove some locale error messages, which break test if we turn on\n+# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n+# on plumbing commands.\n+make_user_friendly_and_stable_output () {\n+\tsed \\\n+\t\t-e \"s/  *\\$//\" \\\n+\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/'/\\\"/g\" \\\n+\t\t-e \"s/\t/    /g\" \\\n+\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n+\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n+\t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n+\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n+\t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n+\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\" \\\n+\t\t-e \"s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#\" \\\n+\t\t-e \"/^error: / d\"\n+}\ndiff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh\nnew file mode 100644\nindex 0000000000..e206587348\n--- /dev/null\n+++ b/t/t5411/test-0000-standard-git-push.sh\n@@ -0,0 +1,143 @@\n+# Refs of upstream : master(A)  \n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t * [new branch] HEAD -> next\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push --atomic: master(A)  next(B)\n+test_expect_success \"git-push --atomic ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\tmaster \\\n+\t\t$B:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out |\n+\t\tsed -n \\\n+\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n+\t\t\t-e \"/^ ! / { s/   */ /g; p; }\" \\\n+\t\t\t>actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [rejected] master -> master (non-fast-forward)\n+\t ! [rejected] <COMMIT-B> -> next (atomic push failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push         : master(A)  next(B)\n+test_expect_success \"non-fast-forward git-push ($PROTOCOL)\" '\n+\ttest_must_fail git \\\n+\t\t-C workbench \\\n+\t\t-c advice.pushUpdateRejected=false \\\n+\t\tpush origin \\\n+\t\tmaster \\\n+\t\t$B:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> next\n+\t ! [rejected] master -> master (non-fast-forward)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-B> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(B)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"git-push -f ($PROTOCOL)\" '\n+\tgit -C workbench push -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tmaster \\\n+\t\tmaster:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-B> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-B> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo <URL/of/upstream.git>\n+\t + <OID-B>...<OID-A> master -> master (forced update)\n+\t - [deleted] next\n+\t * [new tag] v123 -> v123\n+\t * [new reference] master -> refs/review/master/topic\n+\t * [new branch] HEAD -> a/b/c\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\ndiff --git a/t/t5411/test-0001-standard-git-push--porcelain.sh b/t/t5411/test-0001-standard-git-push--porcelain.sh\nnew file mode 100644\nindex 0000000000..48f6fcc846\n--- /dev/null\n+++ b/t/t5411/test-0001-standard-git-push--porcelain.sh\n@@ -0,0 +1,147 @@\n+# Refs of upstream : master(A)  \n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push --atomic: master(A)  next(B)\n+test_expect_success \"git-push --atomic ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --atomic --porcelain origin \\\n+\t\tmaster \\\n+\t\t$B:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out |\n+\t\tsed -n \\\n+\t\t\t-e \"s/^# GETTEXT POISON #//\" \\\n+\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n+\t\t\t-e \"/^! / { s/   */ /g; p; }\" \\\n+\t\t\t>actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t! refs/heads/master:refs/heads/master [rejected] (non-fast-forward)\n+\t! <COMMIT-B>:refs/heads/next [rejected] (atomic push failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push         : master(A)  next(B)\n+test_expect_success \"non-fast-forward git-push ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git \\\n+\t\t-C workbench \\\n+\t\t-c advice.pushUpdateRejected=false \\\n+\t\tpush --porcelain origin \\\n+\t\tmaster \\\n+\t\t$B:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/next    <OID-A>..<OID-B>\n+\t!    refs/heads/master:refs/heads/master    [rejected] (non-fast-forward)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-B> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(B)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"git-push -f ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tmaster \\\n+\t\tmaster:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-B> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-B> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo <URL/of/upstream.git>\n+\t+    refs/heads/master:refs/heads/master    <OID-B>...<OID-A> (forced update)\n+\t-    :refs/heads/next    [deleted]\n+\t*    refs/tags/v123:refs/tags/v123    [new tag]\n+\t*    refs/heads/master:refs/review/master/topic    [new reference]\n+\t*    HEAD:refs/heads/a/b/c    [new branch]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\ndiff --git a/t/t5411/test-0002-pre-receive-declined.sh b/t/t5411/test-0002-pre-receive-declined.sh\nnew file mode 100644\nindex 0000000000..c246f7e68e\n--- /dev/null\n+++ b/t/t5411/test-0002-pre-receive-declined.sh\n@@ -0,0 +1,33 @@\n+test_expect_success \"setup pre-receive hook ($PROTOCOL)\" '\n+\tmv \"$upstream/hooks/pre-receive\" \"$upstream/hooks/pre-receive.ok\" &&\n+\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n+\texit 1\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push is declined ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] <COMMIT-B> -> master (pre-receive hook declined)\n+\t ! [remote rejected] HEAD -> next (pre-receive hook declined)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tmv \"$upstream/hooks/pre-receive.ok\" \"$upstream/hooks/pre-receive\"\n+'\ndiff --git a/t/t5411/test-0003-pre-receive-declined--porcelain.sh b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\nnew file mode 100644\nindex 0000000000..b14894de81\n--- /dev/null\n+++ b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\n@@ -0,0 +1,34 @@\n+test_expect_success \"setup pre-receive hook ($PROTOCOL/porcelain)\" '\n+\tmv \"$upstream/hooks/pre-receive\" \"$upstream/hooks/pre-receive.ok\" &&\n+\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n+\texit 1\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push is declined ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    <COMMIT-B>:refs/heads/master    [remote rejected] (pre-receive hook declined)\n+\t!    HEAD:refs/heads/next    [remote rejected] (pre-receive hook declined)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tmv \"$upstream/hooks/pre-receive.ok\" \"$upstream/hooks/pre-receive\"\n+'\n-- \n2.26.2.543.ge7de2a6e37\n\n"},{"id":"404337","messageId":"20200824174202.11710-4-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200815171740.6257-1-worldhello.net@gmail.com","subject":"[PATCH v18 03/10] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-24T17:41:55Z","receivedAt":"2020-08-24T17:42:34Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nGit calls an internal `execute_commands` function to handle commands\nsent from client to `git-receive-pack`.  Regardless of what references\nthe user pushes, git creates or updates the corresponding references if\nthe user has write-permission.  A contributor who has no\nwrite-permission, cannot push to the repository directly.  So, the\ncontributor has to write commits to an alternate location, and sends\npull request by emails or by other ways.  We call this workflow as a\ndistributed workflow.\n\nIt would be more convenient to work in a centralized workflow like what\nGerrit provided for some cases.  For example, a read-only user who\ncannot push to a branch directly can run the following `git push`\ncommand to push commits to a pseudo reference (has a prefix \"refs/for/\",\nnot \"refs/heads/\") to create a code review.\n\n    git push origin \\\n        HEAD:refs/for/<branch-name>/<session>\n\nThe `<branch-name>` in the above example can be as simple as \"master\",\nor a more complicated branch name like \"foo/bar\".  The `<session>` in\nthe above example command can be the local branch name of the client\nside, such as \"my/topic\".\n\nWe cannot implement a centralized workflow elegantly by using\n\"pre-receive\" + \"post-receive\", because Git will call the internal\nfunction \"execute_commands\" to create references (even the special\npseudo reference) between these two hooks.  Even though we can delete\nthe temporarily created pseudo reference via the \"post-receive\" hook,\nhaving a temporary reference is not safe for concurrent pushes.\n\nSo, add a filter and a new handler to support this kind of workflow.\nThe filter will check the prefix of the reference name, and if the\ncommand has a special reference name, the filter will turn a specific\nfield (`run_proc_receive`) on for the command.  Commands with this filed\nturned on will be executed by a new handler (an hook named\n\"proc-receive\") instead of the internal `execute_commands` function.\nWe can use this \"proc-receive\" command to create pull requests or send\nemails for code review.\n\nSuggested by Junio, this \"proc-receive\" hook reads the commands,\npush-options (optional), and send result using a protocol in pkt-line\nformat.  In the following example, The letter \"S\" stands for\n\"receive-pack\" and letter \"H\" stands for the hook.\n\n    # Version and features negotiation.\n    S: PKT-LINE(version=1\\0push-options atomic...)\n    S: flush-pkt\n    H: PKT-LINE(version=1\\0push-options...)\n    H: flush-pkt\n\n    # Send commands from server to the hook.\n    S: PKT-LINE(<old-oid> <new-oid> <ref>)\n    S: ... ...\n    S: flush-pkt\n    # Send push-options only if the 'push-options' feature is enabled.\n    S: PKT-LINE(push-option)\n    S: ... ...\n    S: flush-pkt\n\n    # Receive result from the hook.\n    # OK, run this command successfully.\n    H: PKT-LINE(ok <ref>)\n    # NO, I reject it.\n    H: PKT-LINE(ng <ref> <reason>)\n    # Fall through, let 'receive-pack' to execute it.\n    H: PKT-LINE(ok <ref>)\n    H: PKT-LINE(option fall-through)\n    # OK, but has an alternate reference.  The alternate reference name\n    # and other status can be given in options\n    H: PKT-LINE(ok <ref>)\n    H: PKT-LINE(option refname <refname>)\n    H: PKT-LINE(option old-oid <old-oid>)\n    H: PKT-LINE(option new-oid <new-oid>)\n    H: PKT-LINE(option forced-update)\n    H: ... ...\n    H: flush-pkt\n\nAfter receiving a command, the hook will execute the command, and may\ncreate/update different reference.  For example, a command for a pseudo\nreference \"refs/for/master/topic\" may create/update different reference\nsuch as \"refs/pull/123/head\".  The alternate reference name and other\nstatus are given in option lines.\n\nThe list of commands returned from \"proc-receive\" will replace the\nrelevant commands that are sent from user to \"receive-pack\", and\n\"receive-pack\" will continue to run the \"execute_commands\" function and\nother routines.  Finally, the result of the execution of these commands\nwill be reported to end user.\n\nThe reporting function from \"receive-pack\" to \"send-pack\" will be\nextended in latter commit just like what the \"proc-receive\" hook reports\nto \"receive-pack\".\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Makefile                                      |   1 +\n builtin/receive-pack.c                        | 296 +++++++++++++++++-\n remote.h                                      |   8 +\n t/helper/test-proc-receive.c                  | 176 +++++++++++\n t/helper/test-tool.c                          |   1 +\n t/helper/test-tool.h                          |   1 +\n t/t5411/test-0011-no-hook-error.sh            |  64 ++++\n t/t5411/test-0012-no-hook-error--porcelain.sh |  66 ++++\n t/t5411/test-0013-bad-protocol.sh             | 217 +++++++++++++\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 160 ++++++++++\n t/t5411/test-0020-report-ng.sh                |  67 ++++\n t/t5411/test-0021-report-ng--porcelain.sh     |  69 ++++\n t/t5411/test-0022-report-unexpect-ref.sh      |  45 +++\n ...est-0023-report-unexpect-ref--porcelain.sh |  46 +++\n t/t5411/test-0024-report-unknown-ref.sh       |  34 ++\n ...test-0025-report-unknown-ref--porcelain.sh |  35 +++\n t/t5411/test-0026-push-options.sh             |  79 +++++\n t/t5411/test-0027-push-options--porcelain.sh  |  82 +++++\n t/t5411/test-0030-report-ok.sh                |  35 +++\n t/t5411/test-0031-report-ok--porcelain.sh     |  36 +++\n t/t5411/test-0032-report-with-options.sh      | 256 +++++++++++++++\n ...est-0033-report-with-options--porcelain.sh | 265 ++++++++++++++++\n t/t5411/test-0034-report-ft.sh                |  44 +++\n t/t5411/test-0035-report-ft--porcelain.sh     |  45 +++\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 159 ++++++++++\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 162 ++++++++++\n t/t5411/test-0038-report-mixed-refs.sh        |  89 ++++++\n .../test-0039-report-mixed-refs--porcelain.sh |  91 ++++++\n 28 files changed, 2626 insertions(+), 3 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100644 t/t5411/test-0011-no-hook-error.sh\n create mode 100644 t/t5411/test-0012-no-hook-error--porcelain.sh\n create mode 100644 t/t5411/test-0013-bad-protocol.sh\n create mode 100644 t/t5411/test-0014-bad-protocol--porcelain.sh\n create mode 100644 t/t5411/test-0020-report-ng.sh\n create mode 100644 t/t5411/test-0021-report-ng--porcelain.sh\n create mode 100644 t/t5411/test-0022-report-unexpect-ref.sh\n create mode 100644 t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n create mode 100644 t/t5411/test-0024-report-unknown-ref.sh\n create mode 100644 t/t5411/test-0025-report-unknown-ref--porcelain.sh\n create mode 100644 t/t5411/test-0026-push-options.sh\n create mode 100644 t/t5411/test-0027-push-options--porcelain.sh\n create mode 100644 t/t5411/test-0030-report-ok.sh\n create mode 100644 t/t5411/test-0031-report-ok--porcelain.sh\n create mode 100644 t/t5411/test-0032-report-with-options.sh\n create mode 100644 t/t5411/test-0033-report-with-options--porcelain.sh\n create mode 100644 t/t5411/test-0034-report-ft.sh\n create mode 100644 t/t5411/test-0035-report-ft--porcelain.sh\n create mode 100644 t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n create mode 100644 t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n create mode 100644 t/t5411/test-0038-report-mixed-refs.sh\n create mode 100644 t/t5411/test-0039-report-mixed-refs--porcelain.sh\n\ndiff --git a/Makefile b/Makefile\nindex 3d3a39fc19..607aeabeee 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -722,6 +722,7 @@ TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\n+TEST_BUILTINS_OBJS += test-proc-receive.o\n TEST_BUILTINS_OBJS += test-progress.o\n TEST_BUILTINS_OBJS += test-reach.o\n TEST_BUILTINS_OBJS += test-read-cache.o\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex a00f91c1a0..2b63b92545 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -308,11 +308,15 @@ static void write_head_info(void)\n \tpacket_flush(1);\n }\n \n+#define RUN_PROC_RECEIVE_SCHEDULED\t1\n+#define RUN_PROC_RECEIVE_RETURNED\t2\n struct command {\n \tstruct command *next;\n \tconst char *error_string;\n+\tstruct ref_push_report *report;\n \tunsigned int skip_update:1,\n-\t\t     did_not_exist:1;\n+\t\t     did_not_exist:1,\n+\t\t     run_proc_receive:2;\n \tint index;\n \tstruct object_id old_oid;\n \tstruct object_id new_oid;\n@@ -838,6 +842,268 @@ static int run_update_hook(struct command *cmd)\n \treturn finish_command(&proc);\n }\n \n+static struct command *find_command_by_refname(struct command *list,\n+\t\t\t\t\t       const char *refname)\n+{\n+\tfor (; list; list = list->next)\n+\t\tif (!strcmp(list->ref_name, refname))\n+\t\t\treturn list;\n+\treturn NULL;\n+}\n+\n+static int read_proc_receive_report(struct packet_reader *reader,\n+\t\t\t\t    struct command *commands,\n+\t\t\t\t    struct strbuf *errmsg)\n+{\n+\tstruct command *cmd;\n+\tstruct command *hint = NULL;\n+\tstruct ref_push_report *report = NULL;\n+\tint new_report = 0;\n+\tint code = 0;\n+\tint once = 0;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *head;\n+\t\tconst char *refname;\n+\t\tchar *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\thead = reader->line;\n+\t\tp = strchr(head, ' ');\n+\t\tif (!p) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported incomplete status line: '%s'\\n\", head);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\t*p++ = '\\0';\n+\t\tif (!strcmp(head, \"option\")) {\n+\t\t\tconst char *key, *val;\n+\n+\t\t\tif (!hint || !(report || new_report)) {\n+\t\t\t\tif (!once++)\n+\t\t\t\t\tstrbuf_addstr(errmsg, \"proc-receive reported 'option' without a matching 'ok/ng' directive\\n\");\n+\t\t\t\tcode = -1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tif (new_report) {\n+\t\t\t\tif (!hint->report) {\n+\t\t\t\t\thint->report = xcalloc(1, sizeof(struct ref_push_report));\n+\t\t\t\t\treport = hint->report;\n+\t\t\t\t} else {\n+\t\t\t\t\treport = hint->report;\n+\t\t\t\t\twhile (report->next)\n+\t\t\t\t\t\treport = report->next;\n+\t\t\t\t\treport->next = xcalloc(1, sizeof(struct ref_push_report));\n+\t\t\t\t\treport = report->next;\n+\t\t\t\t}\n+\t\t\t\tnew_report = 0;\n+\t\t\t}\n+\t\t\tkey = p;\n+\t\t\tp = strchr(key, ' ');\n+\t\t\tif (p)\n+\t\t\t\t*p++ = '\\0';\n+\t\t\tval = p;\n+\t\t\tif (!strcmp(key, \"refname\"))\n+\t\t\t\treport->ref_name = xstrdup_or_null(val);\n+\t\t\telse if (!strcmp(key, \"old-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &old_oid, &val))\n+\t\t\t\treport->old_oid = oiddup(&old_oid);\n+\t\t\telse if (!strcmp(key, \"new-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &new_oid, &val))\n+\t\t\t\treport->new_oid = oiddup(&new_oid);\n+\t\t\telse if (!strcmp(key, \"forced-update\"))\n+\t\t\t\treport->forced_update = 1;\n+\t\t\telse if (!strcmp(key, \"fall-through\"))\n+\t\t\t\t/* Fall through, let 'receive-pack' to execute it. */\n+\t\t\t\thint->run_proc_receive = 0;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\treport = NULL;\n+\t\tnew_report = 0;\n+\t\trefname = p;\n+\t\tp = strchr(refname, ' ');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n+\t\tif (strcmp(head, \"ok\") && strcmp(head, \"ng\")) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported bad status '%s' on ref '%s'\\n\",\n+\t\t\t\t    head, refname);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/* first try searching at our hint, falling back to all refs */\n+\t\tif (hint)\n+\t\t\thint = find_command_by_refname(hint, refname);\n+\t\tif (!hint)\n+\t\t\thint = find_command_by_refname(commands, refname);\n+\t\tif (!hint) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unknown ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!hint->run_proc_receive) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unexpected ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n+\t\tif (!strcmp(head, \"ng\")) {\n+\t\t\tif (p)\n+\t\t\t\thint->error_string = xstrdup(p);\n+\t\t\telse\n+\t\t\t\thint->error_string = \"failed\";\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tnew_report = 1;\n+\t}\n+\n+\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\tif (cmd->run_proc_receive && !cmd->error_string &&\n+\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {\n+\t\t    cmd->error_string = \"proc-receive failed to report status\";\n+\t\t    code = -1;\n+\t\t}\n+\treturn code;\n+}\n+\n+static int run_proc_receive_hook(struct command *commands,\n+\t\t\t\t const struct string_list *push_options)\n+{\n+\tstruct child_process proc = CHILD_PROCESS_INIT;\n+\tstruct async muxer;\n+\tstruct command *cmd;\n+\tconst char *argv[2];\n+\tstruct packet_reader reader;\n+\tstruct strbuf cap = STRBUF_INIT;\n+\tstruct strbuf errmsg = STRBUF_INIT;\n+\tint hook_use_push_options = 0;\n+\tint version = 0;\n+\tint code;\n+\n+\targv[0] = find_hook(\"proc-receive\");\n+\tif (!argv[0]) {\n+\t\trp_error(\"cannot find hook 'proc-receive'\");\n+\t\treturn -1;\n+\t}\n+\targv[1] = NULL;\n+\n+\tproc.argv = argv;\n+\tproc.in = -1;\n+\tproc.out = -1;\n+\tproc.trace2_hook_name = \"proc-receive\";\n+\n+\tif (use_sideband) {\n+\t\tmemset(&muxer, 0, sizeof(muxer));\n+\t\tmuxer.proc = copy_to_sideband;\n+\t\tmuxer.in = -1;\n+\t\tcode = start_async(&muxer);\n+\t\tif (code)\n+\t\t\treturn code;\n+\t\tproc.err = muxer.in;\n+\t} else {\n+\t\tproc.err = 0;\n+\t}\n+\n+\tcode = start_command(&proc);\n+\tif (code) {\n+\t\tif (use_sideband)\n+\t\t\tfinish_async(&muxer);\n+\t\treturn code;\n+\t}\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\n+\t/* Version negotiaton */\n+\tpacket_reader_init(&reader, proc.out, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_GENTLE_ON_EOF);\n+\tif (use_atomic)\n+\t\tstrbuf_addstr(&cap, \" atomic\");\n+\tif (use_push_options)\n+\t\tstrbuf_addstr(&cap, \" push-options\");\n+\tif (cap.len) {\n+\t\tpacket_write_fmt(proc.in, \"version=1%c%s\\n\", '\\0', cap.buf + 1);\n+\t\tstrbuf_release(&cap);\n+\t} else {\n+\t\tpacket_write_fmt(proc.in, \"version=1\\n\");\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader.pktlen > 8 && starts_with(reader.line, \"version=\")) {\n+\t\t\tversion = atoi(reader.line + 8);\n+\t\t\tlinelen = strlen(reader.line);\n+\t\t\tif (linelen < reader.pktlen) {\n+\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\thook_use_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (version != 1) {\n+\t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n+\t\t\t    version);\n+\t\tcode = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\t/* Send commands */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)\n+\t\t\tcontinue;\n+\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n+\t\t\t\t oid_to_hex(&cmd->old_oid),\n+\t\t\t\t oid_to_hex(&cmd->new_oid),\n+\t\t\t\t cmd->ref_name);\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\t/* Send push options */\n+\tif (hook_use_push_options) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tfor_each_string_list_item(item, push_options)\n+\t\t\tpacket_write_fmt(proc.in, \"%s\", item->string);\n+\t\tpacket_flush(proc.in);\n+\t}\n+\n+\t/* Read result from proc-receive */\n+\tcode = read_proc_receive_report(&reader, commands, &errmsg);\n+\n+cleanup:\n+\tclose(proc.in);\n+\tclose(proc.out);\n+\tif (use_sideband)\n+\t\tfinish_async(&muxer);\n+\tif (finish_command(&proc))\n+\t\tcode = -1;\n+\tif (errmsg.len >0) {\n+\t\tchar *p = errmsg.buf;\n+\n+\t\tp += errmsg.len - 1;\n+\t\tif (*p == '\\n')\n+\t\t\t*p = '\\0';\n+\t\trp_error(\"%s\", errmsg.buf);\n+\t\tstrbuf_release(&errmsg);\n+\t}\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn code;\n+}\n+\n static char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n@@ -1413,7 +1679,7 @@ static void execute_commands_non_atomic(struct command *commands,\n \tstruct strbuf err = STRBUF_INIT;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\ttransaction = ref_transaction_begin(&err);\n@@ -1453,7 +1719,7 @@ static void execute_commands_atomic(struct command *commands,\n \t}\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\tcmd->error_string = update(cmd, si);\n@@ -1489,6 +1755,7 @@ static void execute_commands(struct command *commands,\n \tstruct iterate_data data;\n \tstruct async muxer;\n \tint err_fd = 0;\n+\tint run_proc_receive = 0;\n \n \tif (unpacker_error) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n@@ -1518,6 +1785,21 @@ static void execute_commands(struct command *commands,\n \n \treject_updates_to_hidden(commands);\n \n+\t/*\n+\t * Try to find commands that have special prefix in their reference names,\n+\t * and mark them to run an external \"proc-receive\" hook later.\n+\t */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!should_process_cmd(cmd))\n+\t\t\tcontinue;\n+\n+\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n+\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n+\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\trun_proc_receive = 1;\n+\t\t}\n+\t}\n+\n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n \t\t\tif (!cmd->error_string)\n@@ -1544,6 +1826,14 @@ static void execute_commands(struct command *commands,\n \tfree(head_name_to_free);\n \thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, NULL, NULL);\n \n+\tif (run_proc_receive &&\n+\t    run_proc_receive_hook(commands, push_options))\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tif (!cmd->error_string &&\n+\t\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED) &&\n+\t\t\t    (cmd->run_proc_receive || use_atomic))\n+\t\t\t\tcmd->error_string = \"fail to run proc-receive hook\";\n+\n \tif (use_atomic)\n \t\texecute_commands_atomic(commands, si);\n \telse\ndiff --git a/remote.h b/remote.h\nindex 11d8719b58..ca7735b441 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -93,6 +93,14 @@ int for_each_remote(each_remote_fn fn, void *priv);\n \n int remote_has_url(struct remote *remote, const char *url);\n \n+struct ref_push_report {\n+\tconst char *ref_name;\n+\tstruct object_id *old_oid;\n+\tstruct object_id *new_oid;\n+\tunsigned int forced_update:1;\n+\tstruct ref_push_report *next;\n+};\n+\n struct ref {\n \tstruct ref *next;\n \tstruct object_id old_oid;\ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nnew file mode 100644\nindex 0000000000..42164d9898\n--- /dev/null\n+++ b/t/helper/test-proc-receive.c\n@@ -0,0 +1,176 @@\n+#include \"cache.h\"\n+#include \"connect.h\"\n+#include \"parse-options.h\"\n+#include \"pkt-line.h\"\n+#include \"sigchain.h\"\n+#include \"test-tool.h\"\n+\n+static const char *proc_receive_usage[] = {\n+\t\"test-tool proc-receive [<options>...]\",\n+\tNULL\n+};\n+\n+static int die_version;\n+static int die_readline;\n+static int no_push_options;\n+static int use_atomic;\n+static int use_push_options;\n+static int verbose;\n+static int version = 1;\n+static struct string_list returns = STRING_LIST_INIT_NODUP;\n+\n+struct command {\n+\tstruct command *next;\n+\tconst char *error_string;\n+\tunsigned int skip_update:1,\n+\t\t     did_not_exist:1;\n+\tint index;\n+\tstruct object_id old_oid;\n+\tstruct object_id new_oid;\n+\tchar ref_name[FLEX_ARRAY]; /* more */\n+};\n+\n+static void proc_receive_verison(struct packet_reader *reader) {\n+\tint server_version = 0;\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader->pktlen > 8 && starts_with(reader->line, \"version=\")) {\n+\t\t\tserver_version = atoi(reader->line+8);\n+\t\t\tlinelen = strlen(reader->line);\n+\t\t\tif (linelen < reader->pktlen) {\n+\t\t\t\tconst char *feature_list = reader->line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"atomic\"))\n+\t\t\t\t\tuse_atomic= 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tuse_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (server_version != 1 || die_version)\n+\t\tdie(\"bad protocol version: %d\", server_version);\n+\n+\tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n+\t\t\t version, '\\0',\n+\t\t\t use_push_options && !no_push_options ? \"push-options\": \"\");\n+\tpacket_flush(1);\n+}\n+\n+static void proc_receive_read_commands(struct packet_reader *reader,\n+\t\t\t\t       struct command **commands)\n+{\n+\tstruct command **tail = commands;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tstruct command *cmd;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    die_readline)\n+\t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n+\t\t\t    reader->line);\n+\t\trefname = p;\n+\t\tFLEX_ALLOC_STR(cmd, ref_name, refname);\n+\t\toidcpy(&cmd->old_oid, &old_oid);\n+\t\toidcpy(&cmd->new_oid, &new_oid);\n+\n+\t\t*tail = cmd;\n+\t\ttail = &cmd->next;\n+\t}\n+}\n+\n+static void proc_receive_read_push_options(struct packet_reader *reader,\n+\t\t\t\t\t   struct string_list *options)\n+{\n+\n+\tif (no_push_options || !use_push_options)\n+\t       return;\n+\n+\twhile (1) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tstring_list_append(options, reader->line);\n+\t}\n+}\n+\n+int cmd__proc_receive(int argc, const char **argv)\n+{\n+\tint nongit_ok = 0;\n+\tstruct packet_reader reader;\n+\tstruct command *commands = NULL;\n+\tstruct string_list push_options = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"no-push-options\", &no_push_options,\n+\t\t\t \"disable push options\"),\n+\t\tOPT_BOOL(0, \"die-version\", &die_version,\n+\t\t\t \"die during version negotiation\"),\n+\t\tOPT_BOOL(0, \"die-readline\", &die_readline,\n+\t\t\t \"die when readline\"),\n+\t\tOPT_STRING_LIST('r', \"return\", &returns, \"old/new/ref/status/msg\",\n+\t\t\t\t\"return of results\"),\n+\t\tOPT__VERBOSE(&verbose, \"be verbose\"),\n+\t\tOPT_INTEGER('V', \"version\", &version,\n+\t\t\t    \"use this protocol version number\"),\n+\t\tOPT_END()\n+\t};\n+\n+\tsetup_git_directory_gently(&nongit_ok);\n+\n+\targc = parse_options(argc, argv, \"test-tools\", options, proc_receive_usage, 0);\n+\tif (argc > 0)\n+\t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n+\tpacket_reader_init(&reader, 0, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\tproc_receive_verison(&reader);\n+\tproc_receive_read_commands(&reader, &commands);\n+\tproc_receive_read_push_options(&reader, &push_options);\n+\n+\tif (verbose) {\n+\t\tstruct command *cmd;\n+\n+\t\tif (use_push_options || use_atomic)\n+\t\t\tfprintf(stderr, \"proc-receive:%s%s\\n\",\n+\t\t\t\tuse_atomic? \" atomic\": \"\",\n+\t\t\t\tuse_push_options ? \" push_options\": \"\");\n+\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tfprintf(stderr, \"proc-receive< %s %s %s\\n\",\n+\t\t\t\toid_to_hex(&cmd->old_oid),\n+\t\t\t\toid_to_hex(&cmd->new_oid),\n+\t\t\t\tcmd->ref_name);\n+\n+\t\tif (push_options.nr > 0)\n+\t\t\tfor_each_string_list_item(item, &push_options)\n+\t\t\t\tfprintf(stderr, \"proc-receive< %s\\n\", item->string);\n+\n+\t\tif (returns.nr)\n+\t\t\tfor_each_string_list_item(item, &returns)\n+\t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n+\t}\n+\n+\tif (returns.nr)\n+\t\tfor_each_string_list_item(item, &returns)\n+\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n+\tpacket_flush(1);\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 590b2efca7..a0d3966b29 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -46,6 +46,7 @@ static struct test_cmd cmds[] = {\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\n+\t{ \"proc-receive\", cmd__proc_receive},\n \t{ \"progress\", cmd__progress },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex ddc8e990e9..07034d3f38 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -35,6 +35,7 @@ int cmd__parse_pathspec_file(int argc, const char** argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\n+int cmd__proc_receive(int argc, const char **argv);\n int cmd__progress(int argc, const char **argv);\n int cmd__reach(int argc, const char **argv);\n int cmd__read_cache(int argc, const char **argv);\ndiff --git a/t/t5411/test-0011-no-hook-error.sh b/t/t5411/test-0011-no-hook-error.sh\nnew file mode 100644\nindex 0000000000..bb6ec92a92\n--- /dev/null\n+++ b/t/t5411/test-0011-no-hook-error.sh\n@@ -0,0 +1,64 @@\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\t$B:master \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] <COMMIT-B> -> master (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0012-no-hook-error--porcelain.sh b/t/t5411/test-0012-no-hook-error--porcelain.sh\nnew file mode 100644\nindex 0000000000..4814f74dc2\n--- /dev/null\n+++ b/t/t5411/test-0012-no-hook-error--porcelain.sh\n@@ -0,0 +1,66 @@\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain --atomic origin \\\n+\t\t$B:master \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t!    <COMMIT-B>:refs/heads/master    [remote rejected] (fail to run proc-receive hook)\n+\t!    HEAD:refs/heads/next    [remote rejected] (fail to run proc-receive hook)\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh\nnew file mode 100644\nindex 0000000000..c5fe4cb37b\n--- /dev/null\n+++ b/t/t5411/test-0013-bad-protocol.sh\n@@ -0,0 +1,217 @@\n+test_expect_success \"setup proc-receive hook (unknown version, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\t# Check status report for git-push\n+\tsed -n \\\n+\t\t-e \"/^To / { p; n; p; }\" \\\n+\t\t<actual >actual-report &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual-report &&\n+\n+\t# Check error message from \"receive-pack\", but ignore unstable fatal error\n+\t# message (\"remote: fatal: the remote end hung up unexpectedly\") which\n+\t# is different from the remote HTTP server with different locale settings.\n+\tgrep \"^remote: error:\" <actual >actual-error &&\n+\tcat >expect <<-EOF &&\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tEOF\n+\ttest_cmp expect actual-error &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-version, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-version\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-version, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: fatal: bad protocol version: 1\n+\tremote: error: proc-receive version \"0\" is not supported\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-readline, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-readline\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-readline, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\tgrep \"remote: fatal: protocol error: expected \\\"old new ref\\\", got \\\"<ZERO-OID> <COMMIT-A> refs/for/master/topic\\\"\" actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok\n+\tremote: error: proc-receive reported incomplete status line: \"ok\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"xx refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> xx refs/for/master/topic\n+\tremote: error: proc-receive reported bad status \"xx\" on ref \"refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0014-bad-protocol--porcelain.sh b/t/t5411/test-0014-bad-protocol--porcelain.sh\nnew file mode 100644\nindex 0000000000..53b47b0185\n--- /dev/null\n+++ b/t/t5411/test-0014-bad-protocol--porcelain.sh\n@@ -0,0 +1,160 @@\n+test_expect_success \"setup proc-receive hook (unknown version, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\t# Check status report for git-push\n+\tsed -n \\\n+\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n+\t\t<actual >actual-report &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual-report &&\n+\n+\t# Check error message from \"receive-pack\", but ignore unstable fatal error\n+\t# message (\"remote: fatal: the remote end hung up unexpectedly\") which\n+\t# is different from the remote HTTP server with different locale settings.\n+\tgrep \"^remote: error:\" <actual >actual-error &&\n+\tcat >expect <<-EOF &&\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tEOF\n+\ttest_cmp expect actual-error &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok\n+\tremote: error: proc-receive reported incomplete status line: \"ok\"\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"xx refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> xx refs/for/master/topic\n+\tremote: error: proc-receive reported bad status \"xx\" on ref \"refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0020-report-ng.sh b/t/t5411/test-0020-report-ng.sh\nnew file mode 100644\nindex 0000000000..f726b7ca9c\n--- /dev/null\n+++ b/t/t5411/test-0020-report-ng.sh\n@@ -0,0 +1,67 @@\n+test_expect_success \"setup proc-receive hook (ng, no message, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic error msg\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0021-report-ng--porcelain.sh b/t/t5411/test-0021-report-ng--porcelain.sh\nnew file mode 100644\nindex 0000000000..fbf5569103\n--- /dev/null\n+++ b/t/t5411/test-0021-report-ng--porcelain.sh\n@@ -0,0 +1,69 @@\n+test_expect_success \"setup proc-receive hook (ng, no message, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (failed)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic error msg\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (error msg)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0022-report-unexpect-ref.sh b/t/t5411/test-0022-report-unexpect-ref.sh\nnew file mode 100644\nindex 0000000000..92a415b929\n--- /dev/null\n+++ b/t/t5411/test-0022-report-unexpect-ref.sh\n@@ -0,0 +1,45 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: error: proc-receive reported status on unexpected ref: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\ndiff --git a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\nnew file mode 100644\nindex 0000000000..acbf93e40a\n--- /dev/null\n+++ b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n@@ -0,0 +1,46 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: error: proc-receive reported status on unexpected ref: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\ndiff --git a/t/t5411/test-0024-report-unknown-ref.sh b/t/t5411/test-0024-report-unknown-ref.sh\nnew file mode 100644\nindex 0000000000..c3946f329a\n--- /dev/null\n+++ b/t/t5411/test-0024-report-unknown-ref.sh\n@@ -0,0 +1,34 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: report unknown reference ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0025-report-unknown-ref--porcelain.sh b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\nnew file mode 100644\nindex 0000000000..d093b1a579\n--- /dev/null\n+++ b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\n@@ -0,0 +1,35 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: report unknown reference ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/a/b/c/my/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh\nnew file mode 100644\nindex 0000000000..d0c4da8b23\n--- /dev/null\n+++ b/t/t5411/test-0026-push-options.sh\n@@ -0,0 +1,79 @@\n+test_expect_success \"setup proc-receive hook and disable push-options ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions false &&\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"proc-receive: not support push options ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n+\t\tactual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"proc-receive: push with options ($PROTOCOL)\" '\n+\tgit -C workbench push \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\ndiff --git a/t/t5411/test-0027-push-options--porcelain.sh b/t/t5411/test-0027-push-options--porcelain.sh\nnew file mode 100644\nindex 0000000000..c89a1e7c57\n--- /dev/null\n+++ b/t/t5411/test-0027-push-options--porcelain.sh\n@@ -0,0 +1,82 @@\n+test_expect_success \"setup proc-receive hook and disable push-options ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions false &&\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"proc-receive: not support push options ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t--porcelain \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n+\t\tactual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"proc-receive: push with options ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push \\\n+\t\t--porcelain \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\ndiff --git a/t/t5411/test-0030-report-ok.sh b/t/t5411/test-0030-report-ok.sh\nnew file mode 100644\nindex 0000000000..44c99d3831\n--- /dev/null\n+++ b/t/t5411/test-0030-report-ok.sh\n@@ -0,0 +1,35 @@\n+test_expect_success \"setup proc-receive hook (ok, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0031-report-ok--porcelain.sh b/t/t5411/test-0031-report-ok--porcelain.sh\nnew file mode 100644\nindex 0000000000..3223b26184\n--- /dev/null\n+++ b/t/t5411/test-0031-report-ok--porcelain.sh\n@@ -0,0 +1,36 @@\n+test_expect_success \"setup proc-receive hook (ok, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nnew file mode 100644\nindex 0000000000..a743aa8018\n--- /dev/null\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -0,0 +1,256 @@\n+test_expect_success \"setup proc-receive hook (option without matching ok, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option without matching ok ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: error: proc-receive reported \"option\" without a matching \"ok/ng\" directive\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and forced-update, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and forced-update ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and old-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid and new-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (report with multiple rewrites, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/a/b/c/topic\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option forced-update\" \\\n+\t\t-r \"option new-oid $A\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/for/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/a/b/c/topic\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option forced-update\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/for/a/b/c/topic\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nnew file mode 100644\nindex 0000000000..439b97b06e\n--- /dev/null\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -0,0 +1,265 @@\n+test_expect_success \"setup proc-receive hook (option without matching ok, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option without matching ok ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: error: proc-receive reported \"option\" without a matching \"ok/ng\" directive\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and forced-update, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and forced-update ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and old-oid, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid and new-oid, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (report with multiple rewrites, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/a/b/c/topic\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option forced-update\" \\\n+\t\t-r \"option new-oid $A\"\n+\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/for/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/a/b/c/topic\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option forced-update\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/next/topic    [new reference]\n+\t*    HEAD:refs/for/a/b/c/topic    [new reference]\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0034-report-ft.sh b/t/t5411/test-0034-report-ft.sh\nnew file mode 100644\nindex 0000000000..aca2b0676c\n--- /dev/null\n+++ b/t/t5411/test-0034-report-ft.sh\n@@ -0,0 +1,44 @@\n+test_expect_success \"setup proc-receive hook (ft, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option fall-through\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(B)\n+test_expect_success \"proc-receive: fall throught, let receive-pack to execute ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option fall-through\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] <COMMIT-B> -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/for/master/topic\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             refs/for/master/topic(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/for/master/topic\n+'\ndiff --git a/t/t5411/test-0035-report-ft--porcelain.sh b/t/t5411/test-0035-report-ft--porcelain.sh\nnew file mode 100644\nindex 0000000000..30ffffb352\n--- /dev/null\n+++ b/t/t5411/test-0035-report-ft--porcelain.sh\n@@ -0,0 +1,45 @@\n+test_expect_success \"setup proc-receive hook (fall-through, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option fall-through\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(B)\n+test_expect_success \"proc-receive: fall throught, let receive-pack to execute ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\t$B:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option fall-through\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    <COMMIT-B>:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/for/master/topic\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             refs/for/master/topic(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/for/master/topic\n+'\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nnew file mode 100644\nindex 0000000000..12acf9ea1f\n--- /dev/null\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -0,0 +1,159 @@\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/23/123/1\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/2\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/23/123/1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/2\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\nnew file mode 100644\nindex 0000000000..f4a2c56e14\n--- /dev/null\n+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n@@ -0,0 +1,162 @@\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/23/123/1\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/2\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/23/123/1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/2\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nnew file mode 100644\nindex 0000000000..1dd6a97a97\n--- /dev/null\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -0,0 +1,89 @@\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/next/topic2\" \\\n+\t\t-r \"ng refs/for/next/topic1 fail to call Web API\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic2 \\\n+\t\tHEAD:refs/for/next/topic1 \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic3 \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n+\tremote: proc-receive> ok refs/for/next/topic2\n+\tremote: proc-receive> ng refs/for/next/topic1 fail to call Web API\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t * [new branch] HEAD -> bar\n+\t * [new branch] HEAD -> baz\n+\t * [new reference] HEAD -> refs/for/next/topic2\n+\t * [new branch] HEAD -> foo\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\t ! [remote rejected] HEAD -> refs/for/next/topic1 (fail to call Web API)\n+\t ! [remote rejected] HEAD -> refs/for/next/topic3 (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit update-ref -d refs/heads/foo &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+'\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nnew file mode 100644\nindex 0000000000..32ebf63dcf\n--- /dev/null\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -0,0 +1,91 @@\n+test_expect_success \"setup proc-receive hook ($PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/next/topic2\" \\\n+\t\t-r \"ng refs/for/next/topic1 fail to call Web API\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic2 \\\n+\t\tHEAD:refs/for/next/topic1 \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic3 \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n+\tremote: proc-receive> ok refs/for/next/topic2\n+\tremote: proc-receive> ng refs/for/next/topic1 fail to call Web API\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n+\t*    HEAD:refs/heads/bar    [new branch]\n+\t*    HEAD:refs/heads/baz    [new branch]\n+\t*    HEAD:refs/for/next/topic2    [new reference]\n+\t*    HEAD:refs/heads/foo    [new branch]\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\t!    HEAD:refs/for/next/topic1    [remote rejected] (fail to call Web API)\n+\t!    HEAD:refs/for/next/topic3    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit update-ref -d refs/heads/foo &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+\n+'\n-- \n2.26.2.543.ge7de2a6e37\n\n"},{"id":"404335","messageId":"20200824174202.11710-5-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200815171740.6257-1-worldhello.net@gmail.com","subject":"[PATCH v18 04/10] receive-pack: feed report options to post-receive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-24T17:41:56Z","receivedAt":"2020-08-24T17:42:35Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen commands are fed to the \"post-receive\" hook, report options will\nbe parsed and the real old-oid, new-oid, reference name will feed to\nthe \"post-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c                        | 30 ++++++++++++++++---\n t/t5411/test-0032-report-with-options.sh      | 14 ++++-----\n ...est-0033-report-with-options--porcelain.sh | 14 ++++-----\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 11 +++++--\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 11 +++++--\n t/t5411/test-0038-report-mixed-refs.sh        |  2 +-\n .../test-0039-report-mixed-refs--porcelain.sh |  2 +-\n 7 files changed, 58 insertions(+), 26 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 2b63b92545..38f230248d 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -694,6 +694,7 @@ static void prepare_push_cert_sha1(struct child_process *proc)\n \n struct receive_hook_feed_state {\n \tstruct command *cmd;\n+\tstruct ref_push_report *report;\n \tint skip_broken;\n \tstruct strbuf buf;\n \tconst struct string_list *push_options;\n@@ -781,11 +782,31 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n \t\tcmd = cmd->next;\n \tif (!cmd)\n \t\treturn -1; /* EOF */\n+\tif (!bufp)\n+\t\treturn 0; /* OK, can feed something. */\n \tstrbuf_reset(&state->buf);\n-\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n-\t\t    oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),\n-\t\t    cmd->ref_name);\n-\tstate->cmd = cmd->next;\n+\tif (!state->report)\n+\t\tstate->report = cmd->report;\n+\tif (state->report) {\n+\t\tstruct object_id *old_oid;\n+\t\tstruct object_id *new_oid;\n+\t\tconst char *ref_name;\n+\n+\t\told_oid = state->report->old_oid ? state->report->old_oid : &cmd->old_oid;\n+\t\tnew_oid = state->report->new_oid ? state->report->new_oid : &cmd->new_oid;\n+\t\tref_name = state->report->ref_name ? state->report->ref_name : cmd->ref_name;\n+\t\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n+\t\t\t    oid_to_hex(old_oid), oid_to_hex(new_oid),\n+\t\t\t    ref_name);\n+\t\tstate->report = state->report->next;\n+\t\tif (!state->report)\n+\t\t\tstate->cmd = cmd->next;\n+\t} else {\n+\t\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n+\t\t\t    oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),\n+\t\t\t    cmd->ref_name);\n+\t\tstate->cmd = cmd->next;\n+\t}\n \tif (bufp) {\n \t\t*bufp = state->buf.buf;\n \t\t*sizep = state->buf.len;\n@@ -804,6 +825,7 @@ static int run_receive_hook(struct command *commands,\n \tstrbuf_init(&state.buf, 0);\n \tstate.cmd = commands;\n \tstate.skip_broken = skip_broken;\n+\tstate.report = NULL;\n \tif (feed_receive_hook(&state, NULL, NULL))\n \t\treturn 0;\n \tstate.cmd = commands;\ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nindex a743aa8018..c559c207fa 100644\n--- a/t/t5411/test-0032-report-with-options.sh\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -54,7 +54,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL)\" '\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/for/master/topic\n \tEOF\n@@ -87,7 +87,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/for/master/topic\n \tEOF\n@@ -121,7 +121,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL)\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/for/master/topic\n \tEOF\n@@ -153,7 +153,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL)\" '\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/for/master/topic\n \tEOF\n@@ -187,7 +187,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL)\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/for/master/topic\n \tEOF\n@@ -237,9 +237,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n \tremote: proc-receive> option forced-update\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/for/next/topic\n \t * [new reference] HEAD -> refs/for/a/b/c/topic\ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nindex 439b97b06e..ea9312234e 100644\n--- a/t/t5411/test-0033-report-with-options--porcelain.sh\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -55,7 +55,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL/porcelain)\"\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/for/master/topic    [new reference]\n \tDone\n@@ -90,7 +90,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/for/master/topic    [new reference]\n \tDone\n@@ -125,7 +125,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL/\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/for/master/topic    [new reference]\n \tDone\n@@ -158,7 +158,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL/porcelain)\"\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/for/master/topic    [new reference]\n \tDone\n@@ -193,7 +193,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL/\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/for/master/topic    [new reference]\n \tDone\n@@ -245,9 +245,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porc\n \tremote: proc-receive> option forced-update\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/for/next/topic    [new reference]\n \t*    HEAD:refs/for/a/b/c/topic    [new reference]\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex 12acf9ea1f..67cc4a8a4c 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -41,7 +41,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/for/master/topic\n \tEOF\n@@ -99,7 +101,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/for/master/topic\n \tEOF\n@@ -145,7 +149,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/for/master/topic\n \tEOF\ndiff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\nindex f4a2c56e14..1dc8551bc6 100644\n--- a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n@@ -41,7 +41,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/for/master/topic    [new reference]\n \tDone\n@@ -100,7 +102,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/for/master/topic    [new reference]\n \tDone\n@@ -147,7 +151,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porc\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/for/master/topic    [new reference]\n \tDone\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nindex 1dd6a97a97..bfc8d586d6 100644\n--- a/t/t5411/test-0038-report-mixed-refs.sh\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -53,7 +53,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t <OID-A>..<OID-B> <COMMIT-B> -> master\n \t * [new branch] HEAD -> bar\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nindex 32ebf63dcf..5d021a4837 100644\n--- a/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -53,7 +53,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcel\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n \t*    HEAD:refs/heads/bar    [new branch]\n-- \n2.26.2.543.ge7de2a6e37\n\n"},{"id":"404336","messageId":"20200824174202.11710-7-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200815171740.6257-1-worldhello.net@gmail.com","subject":"[PATCH v18 06/10] doc: add document for capability report-status-v2","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-24T17:41:58Z","receivedAt":"2020-08-24T17:42:37Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd ABNF notation for capability 'report-status-v2' which extends\ncapability 'report-status' by adding additional option lines.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/technical/pack-protocol.txt     | 43 +++++++++++++++++--\n .../technical/protocol-capabilities.txt       | 17 ++++++--\n 2 files changed, 54 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt\nindex d5ce4eea8a..7547b9ca89 100644\n--- a/Documentation/technical/pack-protocol.txt\n+++ b/Documentation/technical/pack-protocol.txt\n@@ -503,8 +503,8 @@ The reference discovery phase is done nearly the same way as it is in the\n fetching protocol. Each reference obj-id and name on the server is sent\n in packet-line format to the client, followed by a flush-pkt.  The only\n real difference is that the capability listing is different - the only\n-possible values are 'report-status', 'delete-refs', 'ofs-delta' and\n-'push-options'.\n+possible values are 'report-status', 'report-status-v2', 'delete-refs',\n+'ofs-delta', 'atomic' and 'push-options'.\n \n Reference Update Request and Packfile Transfer\n ----------------------------------------------\n@@ -625,7 +625,7 @@ Report Status\n -------------\n \n After receiving the pack data from the sender, the receiver sends a\n-report if 'report-status' capability is in effect.\n+report if 'report-status' or 'report-status-v2' capability is in effect.\n It is a short listing of what happened in that update.  It will first\n list the status of the packfile unpacking as either 'unpack ok' or\n 'unpack [error]'.  Then it will list the status for each of the references\n@@ -647,6 +647,43 @@ update was successful, or 'ng [refname] [error]' if the update was not.\n   error-msg         = 1*(OCTET) ; where not \"ok\"\n ----\n \n+New capability \"report-status-v2\" extends the protocol by adding new option\n+lines in order to support reporting of reference rewritten by the\n+\"proc-receive\" hook.  The \"proc-receive\" hook may handle a command for a\n+pseudo-reference which may create or update one or more references, and each\n+reference may have different name, different new-oid, and different old-oid.\n+\n+----\n+  report-status-v2  = unpack-status\n+\t\t      1*(command-status-v2)\n+\t\t      flush-pkt\n+\n+  unpack-status     = PKT-LINE(\"unpack\" SP unpack-result)\n+  unpack-result     = \"ok\" / error-msg\n+\n+  command-status-v2 = command-ok-v2 / command-fail-v2\n+  command-ok-v2     = command-ok\n+\t\t      *option-line\n+  command-fail-v2   = command-fail\n+\t\t      *option-line\n+\n+  command-ok        = PKT-LINE(\"ok\" SP refname)\n+  command-fail      = PKT-LINE(\"ng\" SP refname SP error-msg)\n+\n+  error-msg         = 1*(OCTET) ; where not \"ok\"\n+\n+  option-line       = *1(option-refname)\n+\t\t      *1(option-old-oid)\n+\t\t      *1(option-new-oid)\n+\t\t      *1(option-forced-update)\n+\n+  option-refname    = PKT-LINE(\"option\" SP \"refname\" SP refname)\n+  option-old-oid    = PKT-LINE(\"option\" SP \"old-oid\" SP obj-id)\n+  option-new-oid    = PKT-LINE(\"option\" SP \"new-oid\" SP obj-id)\n+  option-force      = PKT-LINE(\"option\" SP \"forced-update\")\n+\n+----\n+\n Updates can be unsuccessful for a number of reasons.  The reference can have\n changed since the reference discovery phase was originally sent, meaning\n someone pushed in the meantime.  The reference being pushed could be a\ndiff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt\nindex 2b267c0da6..91fd976094 100644\n--- a/Documentation/technical/protocol-capabilities.txt\n+++ b/Documentation/technical/protocol-capabilities.txt\n@@ -22,9 +22,9 @@ was sent.  Server MUST NOT ignore capabilities that client requested\n and server advertised.  As a consequence of these rules, server MUST\n NOT advertise capabilities it does not understand.\n \n-The 'atomic', 'report-status', 'delete-refs', 'quiet', and 'push-cert'\n-capabilities are sent and recognized by the receive-pack (push to server)\n-process.\n+The 'atomic', 'report-status', 'report-status-v2', 'delete-refs', 'quiet',\n+and 'push-cert' capabilities are sent and recognized by the receive-pack\n+(push to server) process.\n \n The 'ofs-delta' and 'side-band-64k' capabilities are sent and recognized\n by both upload-pack and receive-pack protocols.  The 'agent' capability\n@@ -269,6 +269,17 @@ each reference was updated successfully.  If any of those were not\n successful, it will send back an error message.  See pack-protocol.txt\n for example messages.\n \n+report-status-v2\n+----------------\n+\n+Capability 'report-status-v2' extends capability 'report-status' by\n+adding new option lines in order to support reference rewritten by\n+the\"proc-receive\" hook.  The \"proc-receive\" hook may handle a command\n+for a pseudo-reference which may create or update a reference with\n+different name, new-oid, and old-oid.  While the capability\n+\"report-status\" cannot report for such case.  See pack-protocol.txt\n+for details.\n+\n delete-refs\n -----------\n \n-- \n2.26.2.543.ge7de2a6e37\n\n"},{"id":"404338","messageId":"20200824174202.11710-8-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200815171740.6257-1-worldhello.net@gmail.com","subject":"[PATCH v18 07/10] receive-pack: new config receive.procReceiveRefs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-24T17:41:59Z","receivedAt":"2020-08-24T17:42:40Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd a new multi-valued config variable \"receive.procReceiveRefs\"\nfor `receive-pack` command, like the follows:\n\n    git config --system --add receive.procReceiveRefs refs/for\n    git config --system --add receive.procReceiveRefs refs/drafts\n\nIf the specific prefix strings given by the config variables match the\nreference names of the commands which are sent from git client to\n`receive-pack`, these commands will be executed by an external hook\n(named \"proc-receive\"), instead of the internal `execute_commands`\nfunction.\n\nFor example, if it is set to \"refs/for\", pushing to a reference such as\n\"refs/for/master\" will not create or update reference \"refs/for/master\",\nbut may create or update a pull request directly by running the hook\n\"proc-receive\".\n\nOptional modifiers can be provided in the beginning of the value to\nfilter commands for specific actions: create (a), mofify (m),\ndelete (d). A `!` can be included in the modifiers to negate the\nreference prefix entry. E.g.:\n\n    git config --system --add receive.procReceiveRefs ad:refs/heads\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/config/receive.txt              |  21 +++\n builtin/receive-pack.c                        | 106 ++++++++++++++-\n t/t5411/once-0010-report-status-v1.sh         |   4 +\n t/t5411/test-0010-proc-receive-settings.sh    |   7 +\n t/t5411/test-0040-process-all-refs.sh         | 113 ++++++++++++++++\n .../test-0041-process-all-refs--porcelain.sh  | 114 ++++++++++++++++\n t/t5411/test-0050-proc-receive-refs.sh        | 128 ++++++++++++++++++\n 7 files changed, 486 insertions(+), 7 deletions(-)\n create mode 100644 t/t5411/test-0010-proc-receive-settings.sh\n create mode 100644 t/t5411/test-0040-process-all-refs.sh\n create mode 100644 t/t5411/test-0041-process-all-refs--porcelain.sh\n create mode 100644 t/t5411/test-0050-proc-receive-refs.sh\n\ndiff --git a/Documentation/config/receive.txt b/Documentation/config/receive.txt\nindex 65f78aac37..86bb9adeb7 100644\n--- a/Documentation/config/receive.txt\n+++ b/Documentation/config/receive.txt\n@@ -114,6 +114,27 @@ receive.hideRefs::\n \tAn attempt to update or delete a hidden ref by `git push` is\n \trejected.\n \n+receive.procReceiveRefs::\n+\tThis is a multi-valued variable that defines reference prefixes\n+\tto match the commands in `receive-pack`.  Commands matching the\n+\tprefixes will be executed by an external hook \"proc-receive\",\n+\tinstead of the internal `execute_commands` function.  If this\n+\tvariable is not defined, the \"proc-receive\" hook will never be\n+\tused, and all commands will be executed by the internal\n+\t`execute_commands` function.\n++\n+For example, if this variable is set to \"refs/for\", pushing to reference\n+such as \"refs/for/master\" will not create or update a reference named\n+\"refs/for/master\", but may create or update a pull request directly by\n+running the hook \"proc-receive\".\n++\n+Optional modifiers can be provided in the beginning of the value to filter\n+commands for specific actions: create (a), mofify (m), delete (d).\n+A `!` can be included in the modifiers to negate the reference prefix entry.\n+E.g.:\n++\n+\tgit config --system --add receive.procReceiveRefs ad:refs/heads\n+\n receive.updateServerInfo::\n \tIf set to true, git-receive-pack will run git-update-server-info\n \tafter receiving data from git-push and updating refs.\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex d6a216b2e3..3eecb8c33c 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -97,6 +97,17 @@ static int keepalive_in_sec = 5;\n \n static struct tmp_objdir *tmp_objdir;\n \n+static struct proc_receive_ref {\n+\tunsigned int want_add:1,\n+\t\t     want_delete:1,\n+\t\t     want_modify:1,\n+\t\t     negative_ref:1;\n+\tchar *ref_prefix;\n+\tstruct proc_receive_ref *next;\n+} *proc_receive_ref;\n+\n+static void proc_receive_ref_append(const char *prefix);\n+\n static enum deny_action parse_deny_action(const char *var, const char *value)\n {\n \tif (value) {\n@@ -229,6 +240,13 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (strcmp(var, \"receive.procreceiverefs\") == 0) {\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tproc_receive_ref_append(value);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -324,6 +342,79 @@ struct command {\n \tchar ref_name[FLEX_ARRAY]; /* more */\n };\n \n+static void proc_receive_ref_append(const char *prefix)\n+{\n+\tstruct proc_receive_ref *ref_pattern;\n+\tchar *p;\n+\tint len;\n+\n+\tref_pattern = xcalloc(1, sizeof(struct proc_receive_ref));\n+\tp = strchr(prefix, ':');\n+\tif (p) {\n+\t\twhile (prefix < p) {\n+\t\t\tif (*prefix == 'a')\n+\t\t\t\tref_pattern->want_add = 1;\n+\t\t\telse if (*prefix == 'd')\n+\t\t\t\tref_pattern->want_delete = 1;\n+\t\t\telse if (*prefix == 'm')\n+\t\t\t\tref_pattern->want_modify = 1;\n+\t\t\telse if (*prefix == '!')\n+\t\t\t\tref_pattern->negative_ref = 1;\n+\t\t\tprefix++;\n+\t\t}\n+\t\tprefix++;\n+\t} else {\n+\t\tref_pattern->want_add = 1;\n+\t\tref_pattern->want_delete = 1;\n+\t\tref_pattern->want_modify = 1;\n+\t}\n+\tlen = strlen(prefix);\n+\twhile (len && prefix[len - 1] == '/')\n+\t\tlen--;\n+\tref_pattern->ref_prefix = xmemdupz(prefix, len);\n+\tif (!proc_receive_ref) {\n+\t\tproc_receive_ref = ref_pattern;\n+\t} else {\n+\t\tstruct proc_receive_ref *end;\n+\n+\t\tend = proc_receive_ref;\n+\t\twhile (end->next)\n+\t\t\tend = end->next;\n+\t\tend->next = ref_pattern;\n+\t}\n+}\n+\n+static int proc_receive_ref_matches(struct command *cmd)\n+{\n+\tstruct proc_receive_ref *p;\n+\n+\tif (!proc_receive_ref)\n+\t\treturn 0;\n+\n+\tfor (p = proc_receive_ref; p; p = p->next) {\n+\t\tconst char *match = p->ref_prefix;\n+\t\tconst char *remains;\n+\n+\t\tif (!p->want_add && is_null_oid(&cmd->old_oid))\n+\t\t\tcontinue;\n+\t\telse if (!p->want_delete && is_null_oid(&cmd->new_oid))\n+\t\t\tcontinue;\n+\t\telse if (!p->want_modify &&\n+\t\t\t !is_null_oid(&cmd->old_oid) &&\n+\t\t\t !is_null_oid(&cmd->new_oid))\n+\t\t\tcontinue;\n+\n+\t\tif (skip_prefix(cmd->ref_name, match, &remains) &&\n+\t\t    (!*remains || *remains == '/')) {\n+\t\t\tif (!p->negative_ref)\n+\t\t\t\treturn 1;\n+\t\t} else if (p->negative_ref) {\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2)));\n static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2)));\n \n@@ -1812,14 +1903,15 @@ static void execute_commands(struct command *commands,\n \t * Try to find commands that have special prefix in their reference names,\n \t * and mark them to run an external \"proc-receive\" hook later.\n \t */\n-\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n-\t\t\tcontinue;\n+\tif (proc_receive_ref) {\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tif (!should_process_cmd(cmd))\n+\t\t\t\tcontinue;\n \n-\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n-\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n-\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n-\t\t\trun_proc_receive = 1;\n+\t\t\tif (proc_receive_ref_matches(cmd)) {\n+\t\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\t\trun_proc_receive = 1;\n+\t\t\t}\n \t\t}\n \t}\n \ndiff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh\nindex bf410dc418..dc2cf4a522 100644\n--- a/t/t5411/once-0010-report-status-v1.sh\n+++ b/t/t5411/once-0010-report-status-v1.sh\n@@ -1,3 +1,7 @@\n+test_expect_success \"setup receive.procReceiveRefs\" '\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs/for\n+'\n+\n test_expect_success \"setup proc-receive hook\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\ndiff --git a/t/t5411/test-0010-proc-receive-settings.sh b/t/t5411/test-0010-proc-receive-settings.sh\nnew file mode 100644\nindex 0000000000..a36809927b\n--- /dev/null\n+++ b/t/t5411/test-0010-proc-receive-settings.sh\n@@ -0,0 +1,7 @@\n+test_expect_success \"add two receive.procReceiveRefs settings\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit config --add receive.procReceiveRefs refs/for &&\n+\t\tgit config --add receive.procReceiveRefs refs/review/\n+\t)\n+'\ndiff --git a/t/t5411/test-0040-process-all-refs.sh b/t/t5411/test-0040-process-all-refs.sh\nnew file mode 100644\nindex 0000000000..b07c999f53\n--- /dev/null\n+++ b/t/t5411/test-0040-process-all-refs.sh\n@@ -0,0 +1,113 @@\n+test_expect_success \"config receive.procReceiveRefs = refs ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config --unset-all receive.procReceiveRefs &&\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup upstream branches ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $B &&\n+\t\tgit update-ref refs/heads/foo $A &&\n+\t\tgit update-ref refs/heads/bar $A &&\n+\t\tgit update-ref refs/heads/baz $A\n+\t)\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/foo\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/bar\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"proc-receive: process all refs ($PROTOCOL)\" '\n+\tgit -C workbench push -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/foo\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/bar\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> bar\n+\t - [deleted] foo\n+\t + <OID-B>...<OID-A> HEAD -> master (forced update)\n+\t <OID-A>..<OID-B> HEAD -> refs/pull/123/head\n+\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             bar(A)  baz(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+'\ndiff --git a/t/t5411/test-0041-process-all-refs--porcelain.sh b/t/t5411/test-0041-process-all-refs--porcelain.sh\nnew file mode 100644\nindex 0000000000..0dd9824616\n--- /dev/null\n+++ b/t/t5411/test-0041-process-all-refs--porcelain.sh\n@@ -0,0 +1,114 @@\n+test_expect_success \"config receive.procReceiveRefs = refs ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" config --unset-all receive.procReceiveRefs &&\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup upstream branches ($PROTOCOL/porcelain)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $B &&\n+\t\tgit update-ref refs/heads/foo $A &&\n+\t\tgit update-ref refs/heads/bar $A &&\n+\t\tgit update-ref refs/heads/baz $A\n+\t)\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook ($PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/foo\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/bar\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"proc-receive: process all refs ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/foo\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/bar\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/bar    <OID-A>..<OID-B>\n+\t-    :refs/heads/foo    [deleted]\n+\t+    HEAD:refs/heads/master    <OID-B>...<OID-A> (forced update)\n+\t     HEAD:refs/pull/123/head    <OID-A>..<OID-B>\n+\t+    HEAD:refs/pull/124/head    <OID-B>...<OID-A> (forced update)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             bar(A)  baz(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+'\ndiff --git a/t/t5411/test-0050-proc-receive-refs.sh b/t/t5411/test-0050-proc-receive-refs.sh\nnew file mode 100644\nindex 0000000000..85e4784678\n--- /dev/null\n+++ b/t/t5411/test-0050-proc-receive-refs.sh\n@@ -0,0 +1,128 @@\n+test_expect_success \"config receive.procReceiveRefs with modifiers ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit config --unset-all receive.procReceiveRefs &&\n+\t\tgit config --add receive.procReceiveRefs m:refs/heads/master &&\n+\t\tgit config --add receive.procReceiveRefs ad:refs/heads &&\n+\t\tgit config --add receive.procReceiveRefs \"a!:refs/heads\"\n+\t)\n+'\n+\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/tags/v123 \" \\\n+\t\t-r \"option refname refs/pull/124/head\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : master(B)  tags/v123\n+test_expect_success \"proc-receive: update branch and new tag ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tv123 >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/tags/v123\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/pull/124/head\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> refs/pull/123/head\n+\t * [new reference] v123 -> refs/pull/124/head\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup upstream: create tags/v123 ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref refs/tags/v123 $TAG &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $ZERO_OID\" \\\n+\t\t-r \"ok refs/heads/next\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option new-oid $A\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)  tags/v123\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : NULL       NULL       next(A)\n+test_expect_success \"proc-receive: create/delete branch, and delete tag ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t:refs/heads/master \\\n+\t\t$A:refs/heads/next \\\n+\t\t:refs/tags/v123 >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/master\n+\tremote: pre-receive< <TAG-v123> <ZERO-OID> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <ZERO-OID>\n+\tremote: proc-receive> ok refs/heads/next\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/pull/123/head\n+\tremote: post-receive< <TAG-v123> <ZERO-OID> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/124/head\n+\tTo <URL/of/upstream.git>\n+\t - [deleted] refs/pull/123/head\n+\t - [deleted] v123\n+\t * [new reference] <COMMIT-A> -> refs/pull/124/head\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n-- \n2.26.2.543.ge7de2a6e37\n\n"},{"id":"404339","messageId":"20200824174202.11710-9-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200815171740.6257-1-worldhello.net@gmail.com","subject":"[PATCH v18 08/10] t5411: test updates of remote-tracking branches","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-24T17:42:00Z","receivedAt":"2020-08-24T17:42:42Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nIn order to test update of remote-tracking branches for special refs,\nadd new \"remote.origin.fetch\" settings and test cases.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 48 +++++++++++++++++++\n 1 file changed, 48 insertions(+)\n\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex 27d58edd14..8eec3a1ed8 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -1,3 +1,18 @@\n+test_expect_success \"setup git config for remote-tracking of special refs\" '\n+\t(\n+\t\tcd workbench &&\n+\t\tif ! git config --get-all remote.origin.fetch | grep refs/for/\n+\t\tthen\n+\t\t\tgit config --add remote.origin.fetch \\\n+\t\t\t\t\"+refs/for/*:refs/t/for/*\" &&\n+\t\t\tgit config --add remote.origin.fetch \\\n+\t\t\t\t\"+refs/pull/*:refs/t/pull/*\" &&\n+\t\t\tgit config --add remote.origin.fetch \\\n+\t\t\t\t\"+refs/changes/*:refs/t/changes/*\"\n+\t\tfi\n+\t)\n+'\n+\n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL)\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n@@ -58,6 +73,17 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \ttest_cmp expect actual\n '\n \n+test_expect_success \"proc-receive: check remote-tracking #1 ($PROTOCOL)\" '\n+\tgit -C workbench show-ref |\n+\t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/t/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C workbench update-ref -d refs/t/for/master/topic\n+'\n+\n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n@@ -120,6 +146,17 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \ttest_cmp expect actual\n '\n \n+test_expect_success \"proc-receive: check remote-tracking #2 ($PROTOCOL)\" '\n+\tgit -C workbench show-ref |\n+\t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/t/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C workbench update-ref -d refs/t/for/master/topic\n+'\n+\n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n@@ -167,3 +204,14 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n \tEOF\n \ttest_cmp expect actual\n '\n+\n+test_expect_success \"proc-receive: check remote-tracking #3 ($PROTOCOL)\" '\n+\tgit -C workbench show-ref |\n+\t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/t/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C workbench update-ref -d refs/t/for/master/topic\n+'\n-- \n2.26.2.543.ge7de2a6e37\n\n"},{"id":"404340","messageId":"20200824174202.11710-10-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200815171740.6257-1-worldhello.net@gmail.com","subject":"[PATCH v18 09/10] transport: parse report options for tracking refs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-24T17:42:01Z","receivedAt":"2020-08-24T17:42:44Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing a pseudo reference (such as \"refs/for/master/topic\"), may\ncreate or update one or more references.  The real names of the\nreferences will be stored in the report options.  Parse report options\nto create or update remote-tracking branches properly.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 22 +++++++---\n transport.c                                   | 40 ++++++++++++++-----\n 2 files changed, 46 insertions(+), 16 deletions(-)\n\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex 8eec3a1ed8..73283d81e8 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -78,10 +78,14 @@ test_expect_success \"proc-receive: check remote-tracking #1 ($PROTOCOL)\" '\n \t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n-\t<COMMIT-A> refs/t/for/master/topic\n+\t<COMMIT-A> refs/t/changes/24/124/1\n+\t<COMMIT-B> refs/t/changes/25/125/1\n+\t<COMMIT-B> refs/t/for/master/topic\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C workbench update-ref -d refs/t/for/master/topic\n+\tgit -C workbench update-ref -d refs/t/for/master/topic &&\n+\tgit -C workbench update-ref -d refs/t/changes/24/124/1 &&\n+\tgit -C workbench update-ref -d refs/t/changes/25/125/1\n '\n \n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)\" '\n@@ -151,10 +155,14 @@ test_expect_success \"proc-receive: check remote-tracking #2 ($PROTOCOL)\" '\n \t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n-\t<COMMIT-A> refs/t/for/master/topic\n+\t<COMMIT-A> refs/t/changes/24/124/1\n+\t<COMMIT-A> refs/t/changes/25/125/1\n+\t<COMMIT-B> refs/t/for/master/topic\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C workbench update-ref -d refs/t/for/master/topic\n+\tgit -C workbench update-ref -d refs/t/for/master/topic &&\n+\tgit -C workbench update-ref -d refs/t/changes/24/124/1 &&\n+\tgit -C workbench update-ref -d refs/t/changes/25/125/1\n '\n \n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)\" '\n@@ -210,8 +218,10 @@ test_expect_success \"proc-receive: check remote-tracking #3 ($PROTOCOL)\" '\n \t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n-\t<COMMIT-A> refs/t/for/master/topic\n+\t<COMMIT-A> refs/t/changes/23/123/1\n+\t<COMMIT-B> refs/t/changes/24/124/2\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C workbench update-ref -d refs/t/for/master/topic\n+\tgit -C workbench update-ref -d refs/t/changes/24/124/1 &&\n+\tgit -C workbench update-ref -d refs/t/changes/25/125/2\n '\ndiff --git a/transport.c b/transport.c\nindex e146de6c4e..7d37baea64 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -437,28 +437,48 @@ int transport_refs_pushed(struct ref *ref)\n \treturn 0;\n }\n \n-void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose)\n+static void update_tracking_ref(struct remote *remote, char *refname,\n+\t\t\t\tstruct object_id *new_oid, int deletion,\n+\t\t\t\tint verbose)\n {\n \tstruct refspec_item rs;\n \n-\tif (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE)\n-\t\treturn;\n-\n-\trs.src = ref->name;\n+\trs.src = refname;\n \trs.dst = NULL;\n-\n \tif (!remote_find_tracking(remote, &rs)) {\n \t\tif (verbose)\n \t\t\tfprintf(stderr, \"updating local tracking ref '%s'\\n\", rs.dst);\n-\t\tif (ref->deletion) {\n+\t\tif (deletion)\n \t\t\tdelete_ref(NULL, rs.dst, NULL, 0);\n-\t\t} else\n-\t\t\tupdate_ref(\"update by push\", rs.dst, &ref->new_oid,\n-\t\t\t\t   NULL, 0, 0);\n+\t\telse\n+\t\t\tupdate_ref(\"update by push\", rs.dst, new_oid,\n+\t\t\t\tNULL, 0, 0);\n \t\tfree(rs.dst);\n \t}\n }\n \n+void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose)\n+{\n+\tchar *refname;\n+\tstruct object_id *new_oid;\n+\tstruct ref_push_report *report;\n+\n+\tif (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE)\n+\t\treturn;\n+\n+\treport = ref->report;\n+\tif (!report)\n+\t\tupdate_tracking_ref(remote, ref->name, &ref->new_oid,\n+\t\t\t\t    ref->deletion, verbose);\n+\telse\n+\t\tfor (; report; report = report->next) {\n+\t\t\trefname = report->ref_name ? (char *)report->ref_name : ref->name;\n+\t\t\tnew_oid = report->new_oid ? report->new_oid : &ref->new_oid;\n+\t\t\tupdate_tracking_ref(remote, refname, new_oid,\n+\t\t\t\t\t    is_null_oid(new_oid), verbose);\n+\t\t}\n+}\n+\n static void print_ref_status(char flag, const char *summary,\n \t\t\t     struct ref *to, struct ref *from, const char *msg,\n \t\t\t     struct ref_push_report *report,\n-- \n2.26.2.543.ge7de2a6e37\n\n"},{"id":"404342","messageId":"20200824174202.11710-6-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200815171740.6257-1-worldhello.net@gmail.com","subject":"[PATCH v18 05/10] New capability \"report-status-v2\" for git-push","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-24T17:41:57Z","receivedAt":"2020-08-24T17:42:47Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThe new introduced \"proc-receive\" hook may handle a command for a\npseudo-reference with a zero-old as its old-oid, while the hook may\ncreate or update a reference with different name, different new-oid,\nand different old-oid (the reference may exist already with a non-zero\nold-oid).  Current \"report-status\" protocol cannot report the status for\nsuch reference rewrite.\n\nAdd new capability \"report-status-v2\" and new report protocol which is\nnot backward compatible for report of git-push.\n\nIf a user pushes to a pseudo-reference \"refs/for/master/topic\", and\n\"receive-pack\" creates two new references \"refs/changes/23/123/1\" and\n\"refs/changes/24/124/1\", for client without the knowledge of\n\"report-status-v2\", \"receive-pack\" will only send \"ok/ng\" directives in\nthe report, such as:\n\n    ok ref/for/master/topic\n\nBut for client which has the knowledge of \"report-status-v2\",\n\"receive-pack\" will use \"option\" directives to report more attributes\nfor the reference given by the above \"ok/ng\" directive.\n\n    ok refs/for/master/topic\n    option refname refs/changes/23/123/1\n    option new-oid <new-oid>\n    ok refs/for/master/topic\n    option refname refs/changes/24/124/1\n    option new-oid <new-oid>\n\nThe client will report two new created references to the end user.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSuggested-by: Jeff King <peff@peff.net>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c                        |  54 +++++++-\n builtin/send-pack.c                           |  19 +++\n remote.h                                      |   1 +\n send-pack.c                                   | 101 +++++++++++---\n t/t5411-proc-receive-hook.sh                  |   9 ++\n t/t5411/once-0010-report-status-v1.sh         |  90 ++++++++++++\n t/t5411/test-0032-report-with-options.sh      |  14 +-\n ...est-0033-report-with-options--porcelain.sh |  14 +-\n ...t-0036-report-multi-rewrite-for-one-ref.sh |  11 +-\n ...rt-multi-rewrite-for-one-ref--porcelain.sh |  11 +-\n t/t5411/test-0038-report-mixed-refs.sh        |   2 +-\n .../test-0039-report-mixed-refs--porcelain.sh |   2 +-\n transport-helper.c                            | 128 ++++++++++++++----\n transport.c                                   | 116 ++++++++++++----\n 14 files changed, 472 insertions(+), 100 deletions(-)\n create mode 100644 t/t5411/once-0010-report-status-v1.sh\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 38f230248d..d6a216b2e3 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -56,6 +56,7 @@ static int advertise_push_options;\n static int unpack_limit = 100;\n static off_t max_input_size;\n static int report_status;\n+static int report_status_v2;\n static int use_sideband;\n static int use_atomic;\n static int use_push_options;\n@@ -239,7 +240,7 @@ static void show_ref(const char *path, const struct object_id *oid)\n \t\tstruct strbuf cap = STRBUF_INIT;\n \n \t\tstrbuf_addstr(&cap,\n-\t\t\t      \"report-status delete-refs side-band-64k quiet\");\n+\t\t\t      \"report-status report-status-v2 delete-refs side-band-64k quiet\");\n \t\tif (advertise_atomic_push)\n \t\t\tstrbuf_addstr(&cap, \" atomic\");\n \t\tif (prefer_ofs_delta)\n@@ -1937,6 +1938,8 @@ static struct command *read_head_info(struct packet_reader *reader,\n \t\t\tconst char *feature_list = reader->line + linelen + 1;\n \t\t\tif (parse_feature_request(feature_list, \"report-status\"))\n \t\t\t\treport_status = 1;\n+\t\t\tif (parse_feature_request(feature_list, \"report-status-v2\"))\n+\t\t\t\treport_status_v2 = 1;\n \t\t\tif (parse_feature_request(feature_list, \"side-band-64k\"))\n \t\t\t\tuse_sideband = LARGE_PACKET_MAX;\n \t\t\tif (parse_feature_request(feature_list, \"quiet\"))\n@@ -2248,6 +2251,51 @@ static void report(struct command *commands, const char *unpack_status)\n \tstrbuf_release(&buf);\n }\n \n+static void report_v2(struct command *commands, const char *unpack_status)\n+{\n+\tstruct command *cmd;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ref_push_report *report;\n+\n+\tpacket_buf_write(&buf, \"unpack %s\\n\",\n+\t\t\t unpack_status ? unpack_status : \"ok\");\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tint count = 0;\n+\n+\t\tif (cmd->error_string) {\n+\t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n+\t\t\t\t\t cmd->ref_name,\n+\t\t\t\t\t cmd->error_string);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n+\t\t\t\t cmd->ref_name);\n+\t\tfor (report = cmd->report; report; report = report->next) {\n+\t\t\tif (count++ > 0)\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n+\t\t\t\t\t\t cmd->ref_name);\n+\t\t\tif (report->ref_name)\n+\t\t\t\tpacket_buf_write(&buf, \"option refname %s\\n\",\n+\t\t\t\t\t\t report->ref_name);\n+\t\t\tif (report->old_oid)\n+\t\t\t\tpacket_buf_write(&buf, \"option old-oid %s\\n\",\n+\t\t\t\t\t\t oid_to_hex(report->old_oid));\n+\t\t\tif (report->new_oid)\n+\t\t\t\tpacket_buf_write(&buf, \"option new-oid %s\\n\",\n+\t\t\t\t\t\t oid_to_hex(report->new_oid));\n+\t\t\tif (report->forced_update)\n+\t\t\t\tpacket_buf_write(&buf, \"option forced-update\\n\");\n+\t\t}\n+\t}\n+\tpacket_buf_flush(&buf);\n+\n+\tif (use_sideband)\n+\t\tsend_sideband(1, 1, buf.buf, buf.len, use_sideband);\n+\telse\n+\t\twrite_or_die(1, buf.buf, buf.len);\n+\tstrbuf_release(&buf);\n+}\n+\n static int delete_only(struct command *commands)\n {\n \tstruct command *cmd;\n@@ -2356,7 +2404,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\t\t\t &push_options);\n \t\tif (pack_lockfile)\n \t\t\tunlink_or_warn(pack_lockfile);\n-\t\tif (report_status)\n+\t\tif (report_status_v2)\n+\t\t\treport_v2(commands, unpack_status);\n+\t\telse if (report_status)\n \t\t\treport(commands, unpack_status);\n \t\trun_receive_hook(commands, \"post-receive\", 1,\n \t\t\t\t &push_options);\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex f2c5a34402..2777cc4371 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -29,10 +29,12 @@ static struct send_pack_args args;\n static void print_helper_status(struct ref *ref)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ref_push_report *report;\n \n \tfor (; ref; ref = ref->next) {\n \t\tconst char *msg = NULL;\n \t\tconst char *res;\n+\t\tint count = 0;\n \n \t\tswitch(ref->status) {\n \t\tcase REF_STATUS_NONE:\n@@ -94,6 +96,23 @@ static void print_helper_status(struct ref *ref)\n \t\t}\n \t\tstrbuf_addch(&buf, '\\n');\n \n+\t\tif (ref->status == REF_STATUS_OK) {\n+\t\t\tfor (report = ref->report; report; report = report->next) {\n+\t\t\t\tif (count++ > 0)\n+\t\t\t\t\tstrbuf_addf(&buf, \"ok %s\\n\", ref->name);\n+\t\t\t\tif (report->ref_name)\n+\t\t\t\t\tstrbuf_addf(&buf, \"option refname %s\\n\",\n+\t\t\t\t\t\treport->ref_name);\n+\t\t\t\tif (report->old_oid)\n+\t\t\t\t\tstrbuf_addf(&buf, \"option old-oid %s\\n\",\n+\t\t\t\t\t\toid_to_hex(report->old_oid));\n+\t\t\t\tif (report->new_oid)\n+\t\t\t\t\tstrbuf_addf(&buf, \"option new-oid %s\\n\",\n+\t\t\t\t\t\toid_to_hex(report->new_oid));\n+\t\t\t\tif (report->forced_update)\n+\t\t\t\t\tstrbuf_addstr(&buf, \"option forced-update\\n\");\n+\t\t\t}\n+\t\t}\n \t\twrite_or_die(1, buf.buf, buf.len);\n \t}\n \tstrbuf_release(&buf);\ndiff --git a/remote.h b/remote.h\nindex ca7735b441..6d1e5c49f8 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -148,6 +148,7 @@ struct ref {\n \t\tREF_STATUS_ATOMIC_PUSH_FAILED\n \t} status;\n \tchar *remote_status;\n+\tstruct ref_push_report *report;\n \tstruct ref *peer_ref; /* when renaming */\n \tchar name[FLEX_ARRAY]; /* more */\n };\ndiff --git a/send-pack.c b/send-pack.c\nindex d1b7edc995..dfaf505c96 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -153,25 +153,79 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n {\n \tstruct ref *hint;\n \tint ret;\n+\tstruct ref_push_report *report = NULL;\n+\tint new_report = 0;\n+\tint once = 0;\n \n \thint = NULL;\n \tret = receive_unpack_status(reader);\n \twhile (1) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *head;\n \t\tconst char *refname;\n-\t\tchar *msg;\n+\t\tchar *p;\n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n \t\t\tbreak;\n-\t\tif (!starts_with(reader->line, \"ok \") && !starts_with(reader->line, \"ng \")) {\n-\t\t\terror(\"invalid ref status from remote: %s\", reader->line);\n+\t\thead = reader->line;\n+\t\tp = strchr(head, ' ');\n+\t\tif (!p) {\n+\t\t\terror(\"invalid status line from remote: %s\", reader->line);\n \t\t\tret = -1;\n \t\t\tbreak;\n \t\t}\n+\t\t*p++ = '\\0';\n \n-\t\trefname = reader->line + 3;\n-\t\tmsg = strchr(refname, ' ');\n-\t\tif (msg)\n-\t\t\t*msg++ = '\\0';\n+\t\tif (!strcmp(head, \"option\")) {\n+\t\t\tconst char *key, *val;\n \n+\t\t\tif (!hint || !(report || new_report)) {\n+\t\t\t\tif (!once++)\n+\t\t\t\t\terror(\"'option' without a matching 'ok/ng' directive\");\n+\t\t\t\tret = -1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tif (new_report) {\n+\t\t\t\tif (!hint->report) {\n+\t\t\t\t\thint->report = xcalloc(1, sizeof(struct ref_push_report));\n+\t\t\t\t\treport = hint->report;\n+\t\t\t\t} else {\n+\t\t\t\t\treport = hint->report;\n+\t\t\t\t\twhile (report->next)\n+\t\t\t\t\t\treport = report->next;\n+\t\t\t\t\treport->next = xcalloc(1, sizeof(struct ref_push_report));\n+\t\t\t\t\treport = report->next;\n+\t\t\t\t}\n+\t\t\t\tnew_report = 0;\n+\t\t\t}\n+\t\t\tkey = p;\n+\t\t\tp = strchr(key, ' ');\n+\t\t\tif (p)\n+\t\t\t\t*p++ = '\\0';\n+\t\t\tval = p;\n+\t\t\tif (!strcmp(key, \"refname\"))\n+\t\t\t\treport->ref_name = xstrdup_or_null(val);\n+\t\t\telse if (!strcmp(key, \"old-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &old_oid, &val))\n+\t\t\t\treport->old_oid = oiddup(&old_oid);\n+\t\t\telse if (!strcmp(key, \"new-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &new_oid, &val))\n+\t\t\t\treport->new_oid = oiddup(&new_oid);\n+\t\t\telse if (!strcmp(key, \"forced-update\"))\n+\t\t\t\treport->forced_update = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\treport = NULL;\n+\t\tnew_report = 0;\n+\t\tif (strcmp(head, \"ok\") && strcmp(head, \"ng\")) {\n+\t\t\terror(\"invalid ref status from remote: %s\", head);\n+\t\t\tret = -1;\n+\t\t\tbreak;\n+\t\t}\n+\t\trefname = p;\n+\t\tp = strchr(refname, ' ');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n \t\t/* first try searching at our hint, falling back to all refs */\n \t\tif (hint)\n \t\t\thint = find_ref_by_name(hint, refname);\n@@ -179,22 +233,27 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \t\t\thint = find_ref_by_name(refs, refname);\n \t\tif (!hint) {\n \t\t\twarning(\"remote reported status on unknown ref: %s\",\n-\t\t\t\t\trefname);\n+\t\t\t\trefname);\n \t\t\tcontinue;\n \t\t}\n-\t\tif (hint->status != REF_STATUS_EXPECTING_REPORT) {\n+\t\tif (hint->status != REF_STATUS_EXPECTING_REPORT &&\n+\t\t    hint->status != REF_STATUS_OK &&\n+\t\t    hint->status != REF_STATUS_REMOTE_REJECT) {\n \t\t\twarning(\"remote reported status on unexpected ref: %s\",\n-\t\t\t\t\trefname);\n+\t\t\t\trefname);\n \t\t\tcontinue;\n \t\t}\n-\n-\t\tif (reader->line[0] == 'o' && reader->line[1] == 'k')\n-\t\t\thint->status = REF_STATUS_OK;\n-\t\telse\n+\t\tif (!strcmp(head, \"ng\")) {\n \t\t\thint->status = REF_STATUS_REMOTE_REJECT;\n-\t\thint->remote_status = xstrdup_or_null(msg);\n-\t\t/* start our next search from the next ref */\n-\t\thint = hint->next;\n+\t\t\tif (p)\n+\t\t\t\thint->remote_status = xstrdup(p);\n+\t\t\telse\n+\t\t\t\thint->remote_status = \"failed\";\n+\t\t} else {\n+\t\t\thint->status = REF_STATUS_OK;\n+\t\t\thint->remote_status = xstrdup_or_null(p);\n+\t\t\tnew_report = 1;\n+\t\t}\n \t}\n \treturn ret;\n }\n@@ -369,7 +428,9 @@ int send_pack(struct send_pack_args *args,\n \tstruct packet_reader reader;\n \n \t/* Does the other end support the reporting? */\n-\tif (server_supports(\"report-status\"))\n+\tif (server_supports(\"report-status-v2\"))\n+\t\tstatus_report = 2;\n+\telse if (server_supports(\"report-status\"))\n \t\tstatus_report = 1;\n \tif (server_supports(\"delete-refs\"))\n \t\tallow_deleting_refs = 1;\n@@ -418,8 +479,10 @@ int send_pack(struct send_pack_args *args,\n \n \tuse_push_options = push_options_supported && args->push_options;\n \n-\tif (status_report)\n+\tif (status_report == 1)\n \t\tstrbuf_addstr(&cap_buf, \" report-status\");\n+\telse if (status_report == 2)\n+\t\tstrbuf_addstr(&cap_buf, \" report-status-v2\");\n \tif (use_sideband)\n \t\tstrbuf_addstr(&cap_buf, \" side-band-64k\");\n \tif (quiet_supported && (args->quiet || !args->progress))\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex 3a684353a8..746487286f 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -78,6 +78,15 @@ run_proc_receive_hook_test() {\n # Initialize the upstream repository and local workbench.\n setup_upstream_and_workbench\n \n+# Load test cases that only need to be executed once.\n+for t in  \"$TEST_DIRECTORY\"/t5411/once-*.sh\n+do\n+\t. \"$t\"\n+done\n+\n+# Initialize the upstream repository and local workbench.\n+setup_upstream_and_workbench\n+\n # Run test cases for 'proc-receive' hook on local file protocol.\n run_proc_receive_hook_test local\n \ndiff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh\nnew file mode 100644\nindex 0000000000..bf410dc418\n--- /dev/null\n+++ b/t/t5411/once-0010-report-status-v1.sh\n@@ -0,0 +1,90 @@\n+test_expect_success \"setup proc-receive hook\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic1\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/for/master/topic2\" \\\n+\t\t-r \"option refname refs/for/changes/23/123/1\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic2\" \\\n+\t\t-r \"option refname refs/for/changes/24/124/2\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\" \\\n+\t\t-r \"ng refs/for/next/topic target branch not exist\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic1(A)  foo(A)  refs/for/next/topic(A)  refs/for/master/topic2(A)\n+test_expect_success \"proc-receive: report status v1\" '\n+\t{\n+\t\tif test -z \"$GIT_DEFAULT_HASH\" || test \"$GIT_DEFAULT_HASH\" = \"sha1\"\n+\t\tthen\n+\t\t\tprintf \"%s %s refs/heads/master\\0report-status\\n\" \\\n+\t\t\t\t$A $B | packetize\n+\t\telse\n+\t\t\tprintf \"%s %s refs/heads/master\\0report-status object-format=$GIT_DEFAULT_HASH\\n\" \\\n+\t\t\t\t$A $B | packetize\n+\t\tfi &&\n+\t\tprintf \"%s %s refs/for/master/topic1\\n\" \\\n+\t\t\t$ZERO_OID $A | packetize &&\n+\t\tprintf \"%s %s refs/heads/foo\\n\" \\\n+\t\t\t$ZERO_OID $A | packetize &&\n+\t\tprintf \"%s %s refs/for/next/topic\\n\" \\\n+\t\t\t$ZERO_OID $A | packetize &&\n+\t\tprintf \"%s %s refs/for/master/topic2\\n\" \\\n+\t\t\t$ZERO_OID $A | packetize &&\n+\t\tprintf 0000 &&\n+\t\tprintf \"\" | git -C \"$upstream\" pack-objects --stdout\n+\t} | git receive-pack \"$upstream\" --stateless-rpc \\\n+\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t# pre-receive hook\n+\tpre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tpre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n+\tpre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tpre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tpre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2\n+\t# proc-receive hook\n+\tproc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n+\tproc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tproc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2\n+\tproc-receive> ok refs/for/master/topic1\n+\tproc-receive> option fall-through\n+\tproc-receive> ok refs/for/master/topic2\n+\tproc-receive> option refname refs/for/changes/23/123/1\n+\tproc-receive> option new-oid <COMMIT-A>\n+\tproc-receive> ok refs/for/master/topic2\n+\tproc-receive> option refname refs/for/changes/24/124/2\n+\tproc-receive> option old-oid <COMMIT-B>\n+\tproc-receive> option new-oid <COMMIT-A>\n+\tproc-receive> option forced-update\n+\tproc-receive> ng refs/for/next/topic target branch not exist\n+\t000eunpack ok\n+\t0019ok refs/heads/master\n+\t001eok refs/for/master/topic1\n+\t0016ok refs/heads/foo\n+\t0033ng refs/for/next/topic target branch not exist\n+\t001eok refs/for/master/topic2\n+\t0000# post-receive hook\n+\tpost-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n+\tpost-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/changes/23/123/1\n+\tpost-receive< <COMMIT-B> <COMMIT-A> refs/for/changes/24/124/2\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/for/master/topic1\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nindex c559c207fa..b77b78c49f 100644\n--- a/t/t5411/test-0032-report-with-options.sh\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -56,7 +56,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL)\" '\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -89,7 +89,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -123,7 +123,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL)\n \tremote: # post-receive hook\n \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-B>..<OID-A> HEAD -> refs/pull/123/head\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -155,7 +155,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL)\" '\n \tremote: # post-receive hook\n \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-B>..<OID-A> HEAD -> refs/for/master/topic\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -189,7 +189,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL)\n \tremote: # post-receive hook\n \tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -241,9 +241,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \t * [new reference] HEAD -> refs/for/a/b/c/topic\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n \ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nindex ea9312234e..1fe352b686 100644\n--- a/t/t5411/test-0033-report-with-options--porcelain.sh\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -57,7 +57,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL/porcelain)\"\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -92,7 +92,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -127,7 +127,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL/\n \tremote: # post-receive hook\n \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/pull/123/head    <OID-B>..<OID-A>\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -160,7 +160,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL/porcelain)\"\n \tremote: # post-receive hook\n \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-B>..<OID-A>\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -195,7 +195,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL/\n \tremote: # post-receive hook\n \tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -249,9 +249,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porc\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/next/topic    [new reference]\n+\t*    HEAD:refs/pull/123/head    [new reference]\n \t*    HEAD:refs/for/a/b/c/topic    [new reference]\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t+    HEAD:refs/pull/124/head    <OID-B>...<OID-A> (forced update)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex 67cc4a8a4c..27d58edd14 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -45,7 +45,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n \tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/24/124/1\n+\t <OID-A>..<OID-B> HEAD -> refs/changes/25/125/1\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -105,7 +107,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/24/124/1\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n+\t + <OID-B>...<OID-A> HEAD -> refs/changes/25/125/1 (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -152,7 +156,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1\n \tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/23/123/1\n+\t <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\ndiff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\nindex 1dc8551bc6..77b5b22ed4 100644\n--- a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n@@ -45,7 +45,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n \tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n+\t*    HEAD:refs/changes/24/124/1    [new reference]\n+\t     HEAD:refs/changes/25/125/1    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n@@ -106,7 +108,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/changes/24/124/1    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n+\t+    HEAD:refs/changes/25/125/1    <OID-B>...<OID-A> (forced update)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n@@ -154,7 +158,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porc\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1\n \tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/changes/23/123/1    [new reference]\n+\t     HEAD:refs/changes/24/124/2    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nindex bfc8d586d6..a74a2cb449 100644\n--- a/t/t5411/test-0038-report-mixed-refs.sh\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -60,7 +60,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n \t * [new branch] HEAD -> baz\n \t * [new reference] HEAD -> refs/for/next/topic2\n \t * [new branch] HEAD -> foo\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \t ! [remote rejected] HEAD -> refs/for/next/topic1 (fail to call Web API)\n \t ! [remote rejected] HEAD -> refs/for/next/topic3 (proc-receive failed to report status)\n \tEOF\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nindex 5d021a4837..e4baa13ea3 100644\n--- a/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -60,7 +60,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcel\n \t*    HEAD:refs/heads/baz    [new branch]\n \t*    HEAD:refs/for/next/topic2    [new reference]\n \t*    HEAD:refs/heads/foo    [new branch]\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \t!    HEAD:refs/for/next/topic1    [remote rejected] (fail to call Web API)\n \t!    HEAD:refs/for/next/topic3    [remote rejected] (proc-receive failed to report status)\n \tDone\ndiff --git a/transport-helper.c b/transport-helper.c\nindex a46afcb69d..1738c81ea0 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -719,13 +719,61 @@ static int fetch(struct transport *transport,\n \treturn -1;\n }\n \n+struct push_update_ref_state {\n+\tstruct ref *hint;\n+\tstruct ref_push_report *report;\n+\tint new_report;\n+};\n+\n static int push_update_ref_status(struct strbuf *buf,\n-\t\t\t\t   struct ref **ref,\n+\t\t\t\t   struct push_update_ref_state *state,\n \t\t\t\t   struct ref *remote_refs)\n {\n \tchar *refname, *msg;\n \tint status, forced = 0;\n \n+\tif (starts_with(buf->buf, \"option \")) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *key, *val;\n+\t\tchar *p;\n+\n+\t\tif (!state->hint || !(state->report || state->new_report))\n+\t\t\tdie(_(\"'option' without a matching 'ok/error' directive\"));\n+\t\tif (state->new_report) {\n+\t\t\tif (!state->hint->report) {\n+\t\t\t\tstate->hint->report = xcalloc(1, sizeof(struct ref_push_report));\n+\t\t\t\tstate->report = state->hint->report;\n+\t\t\t} else {\n+\t\t\t\tstate->report = state->hint->report;\n+\t\t\t\twhile (state->report->next)\n+\t\t\t\t\tstate->report = state->report->next;\n+\t\t\t\tstate->report->next = xcalloc(1, sizeof(struct ref_push_report));\n+\t\t\t\tstate->report = state->report->next;\n+\t\t\t}\n+\t\t\tstate->new_report = 0;\n+\t\t}\n+\t\tkey = buf->buf + 7;\n+\t\tp = strchr(key, ' ');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n+\t\tval = p;\n+\t\tif (!strcmp(key, \"refname\"))\n+\t\t\tstate->report->ref_name = xstrdup_or_null(val);\n+\t\telse if (!strcmp(key, \"old-oid\") && val &&\n+\t\t\t !parse_oid_hex(val, &old_oid, &val))\n+\t\t\tstate->report->old_oid = oiddup(&old_oid);\n+\t\telse if (!strcmp(key, \"new-oid\") && val &&\n+\t\t\t !parse_oid_hex(val, &new_oid, &val))\n+\t\t\tstate->report->new_oid = oiddup(&new_oid);\n+\t\telse if (!strcmp(key, \"forced-update\"))\n+\t\t\tstate->report->forced_update = 1;\n+\t\t/* Not update remote namespace again. */\n+\t\treturn 1;\n+\t}\n+\n+\tstate->report = NULL;\n+\tstate->new_report = 0;\n+\n \tif (starts_with(buf->buf, \"ok \")) {\n \t\tstatus = REF_STATUS_OK;\n \t\trefname = buf->buf + 3;\n@@ -781,16 +829,16 @@ static int push_update_ref_status(struct strbuf *buf,\n \t\t}\n \t}\n \n-\tif (*ref)\n-\t\t*ref = find_ref_by_name(*ref, refname);\n-\tif (!*ref)\n-\t\t*ref = find_ref_by_name(remote_refs, refname);\n-\tif (!*ref) {\n+\tif (state->hint)\n+\t\tstate->hint = find_ref_by_name(state->hint, refname);\n+\tif (!state->hint)\n+\t\tstate->hint = find_ref_by_name(remote_refs, refname);\n+\tif (!state->hint) {\n \t\twarning(_(\"helper reported unexpected status of %s\"), refname);\n \t\treturn 1;\n \t}\n \n-\tif ((*ref)->status != REF_STATUS_NONE) {\n+\tif (state->hint->status != REF_STATUS_NONE) {\n \t\t/*\n \t\t * Earlier, the ref was marked not to be pushed, so ignore the ref\n \t\t * status reported by the remote helper if the latter is 'no match'.\n@@ -799,9 +847,11 @@ static int push_update_ref_status(struct strbuf *buf,\n \t\t\treturn 1;\n \t}\n \n-\t(*ref)->status = status;\n-\t(*ref)->forced_update |= forced;\n-\t(*ref)->remote_status = msg;\n+\tif (status == REF_STATUS_OK)\n+\t\tstate->new_report = 1;\n+\tstate->hint->status = status;\n+\tstate->hint->forced_update |= forced;\n+\tstate->hint->remote_status = msg;\n \treturn !(status == REF_STATUS_OK);\n }\n \n@@ -809,37 +859,57 @@ static int push_update_refs_status(struct helper_data *data,\n \t\t\t\t    struct ref *remote_refs,\n \t\t\t\t    int flags)\n {\n+\tstruct ref *ref;\n+\tstruct ref_push_report *report;\n \tstruct strbuf buf = STRBUF_INIT;\n-\tstruct ref *ref = remote_refs;\n-\tint ret = 0;\n+\tstruct push_update_ref_state state = { remote_refs, NULL, 0 };\n \n \tfor (;;) {\n-\t\tchar *private;\n-\n \t\tif (recvline(data, &buf)) {\n-\t\t\tret = 1;\n-\t\t\tbreak;\n+\t\t\tstrbuf_release(&buf);\n+\t\t\treturn 1;\n \t\t}\n-\n \t\tif (!buf.len)\n \t\t\tbreak;\n+\t\tpush_update_ref_status(&buf, &state, remote_refs);\n+\t}\n+\tstrbuf_release(&buf);\n \n-\t\tif (push_update_ref_status(&buf, &ref, remote_refs))\n-\t\t\tcontinue;\n+\tif (flags & TRANSPORT_PUSH_DRY_RUN || !data->rs.nr || data->no_private_update)\n+\t\treturn 0;\n \n-\t\tif (flags & TRANSPORT_PUSH_DRY_RUN || !data->rs.nr || data->no_private_update)\n-\t\t\tcontinue;\n+\t/* propagate back the update to the remote namespace */\n+\tfor (ref = remote_refs; ref; ref = ref->next) {\n+\t\tchar *private;\n \n-\t\t/* propagate back the update to the remote namespace */\n-\t\tprivate = apply_refspecs(&data->rs, ref->name);\n-\t\tif (!private)\n+\t\tif (ref->status != REF_STATUS_OK)\n \t\t\tcontinue;\n-\t\tupdate_ref(\"update by helper\", private, &ref->new_oid, NULL,\n-\t\t\t   0, 0);\n-\t\tfree(private);\n+\n+\t\tif (!ref->report) {\n+\t\t\tprivate = apply_refspecs(&data->rs, ref->name);\n+\t\t\tif (!private)\n+\t\t\t\tcontinue;\n+\t\t\tupdate_ref(\"update by helper\", private, &(ref->new_oid),\n+\t\t\t\t   NULL, 0, 0);\n+\t\t\tfree(private);\n+\t\t} else {\n+\t\t\tfor (report = ref->report; report; report = report->next) {\n+\t\t\t\tprivate = apply_refspecs(&data->rs,\n+\t\t\t\t\t\t\t report->ref_name\n+\t\t\t\t\t\t\t ? report->ref_name\n+\t\t\t\t\t\t\t : ref->name);\n+\t\t\t\tif (!private)\n+\t\t\t\t\tcontinue;\n+\t\t\t\tupdate_ref(\"update by helper\", private,\n+\t\t\t\t\t   report->new_oid\n+\t\t\t\t\t   ? report->new_oid\n+\t\t\t\t\t   : &(ref->new_oid),\n+\t\t\t\t\t   NULL, 0, 0);\n+\t\t\t\tfree(private);\n+\t\t\t}\n+\t\t}\n \t}\n-\tstrbuf_release(&buf);\n-\treturn ret;\n+\treturn 0;\n }\n \n static void set_common_push_options(struct transport *transport,\ndiff --git a/transport.c b/transport.c\nindex 26fefd13c1..e146de6c4e 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -461,13 +461,21 @@ void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int v\n \n static void print_ref_status(char flag, const char *summary,\n \t\t\t     struct ref *to, struct ref *from, const char *msg,\n+\t\t\t     struct ref_push_report *report,\n \t\t\t     int porcelain, int summary_width)\n {\n+\tconst char *to_name;\n+\n+\tif (report && report->ref_name)\n+\t\tto_name = report->ref_name;\n+\telse\n+\t\tto_name = to->name;\n+\n \tif (porcelain) {\n \t\tif (from)\n-\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to_name);\n \t\telse\n-\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to_name);\n \t\tif (msg)\n \t\t\tfprintf(stdout, \"%s (%s)\\n\", summary, msg);\n \t\telse\n@@ -481,9 +489,11 @@ static void print_ref_status(char flag, const char *summary,\n \t\tfprintf(stderr, \" %s%c %-*s%s \", red, flag, summary_width,\n \t\t\tsummary, reset);\n \t\tif (from)\n-\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(to->name));\n+\t\t\tfprintf(stderr, \"%s -> %s\",\n+\t\t\t\tprettify_refname(from->name),\n+\t\t\t\tprettify_refname(to_name));\n \t\telse\n-\t\t\tfputs(prettify_refname(to->name), stderr);\n+\t\t\tfputs(prettify_refname(to_name), stderr);\n \t\tif (msg) {\n \t\t\tfputs(\" (\", stderr);\n \t\t\tfputs(msg, stderr);\n@@ -493,27 +503,52 @@ static void print_ref_status(char flag, const char *summary,\n \t}\n }\n \n-static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_width)\n+static void print_ok_ref_status(struct ref *ref,\n+\t\t\t\tstruct ref_push_report *report,\n+\t\t\t\tint porcelain, int summary_width)\n {\n+\tstruct object_id *old_oid;\n+\tstruct object_id *new_oid;\n+\tconst char *ref_name;\n+\tint forced_update;\n+\n+\tif (report && report->old_oid)\n+\t\told_oid = report->old_oid;\n+\telse\n+\t\told_oid = &ref->old_oid;\n+\tif (report && report->new_oid)\n+\t\tnew_oid = report->new_oid;\n+\telse\n+\t\tnew_oid = &ref->new_oid;\n+\tif (report && report->forced_update)\n+\t\tforced_update = report->forced_update;\n+\telse\n+\t\tforced_update = ref->forced_update;\n+\tif (report && report->ref_name)\n+\t\tref_name = report->ref_name;\n+\telse\n+\t\tref_name = ref->name;\n+\n \tif (ref->deletion)\n \t\tprint_ref_status('-', \"[deleted]\", ref, NULL, NULL,\n-\t\t\t\t porcelain, summary_width);\n-\telse if (is_null_oid(&ref->old_oid))\n+\t\t\t\t report, porcelain, summary_width);\n+\telse if (is_null_oid(old_oid))\n \t\tprint_ref_status('*',\n-\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t (starts_with(ref_name, \"refs/tags/\")\n \t\t\t\t  ? \"[new tag]\"\n-\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t  : (starts_with(ref_name, \"refs/heads/\")\n \t\t\t\t     ? \"[new branch]\"\n \t\t\t\t     : \"[new reference]\")),\n-\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t ref, ref->peer_ref, NULL,\n+\t\t\t\t report, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n \t\tconst char *msg;\n \n-\t\tstrbuf_add_unique_abbrev(&quickref, &ref->old_oid,\n+\t\tstrbuf_add_unique_abbrev(&quickref, old_oid,\n \t\t\t\t\t DEFAULT_ABBREV);\n-\t\tif (ref->forced_update) {\n+\t\tif (forced_update) {\n \t\t\tstrbuf_addstr(&quickref, \"...\");\n \t\t\ttype = '+';\n \t\t\tmsg = \"forced update\";\n@@ -522,16 +557,17 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\t\ttype = ' ';\n \t\t\tmsg = NULL;\n \t\t}\n-\t\tstrbuf_add_unique_abbrev(&quickref, &ref->new_oid,\n+\t\tstrbuf_add_unique_abbrev(&quickref, new_oid,\n \t\t\t\t\t DEFAULT_ABBREV);\n \n \t\tprint_ref_status(type, quickref.buf, ref, ref->peer_ref, msg,\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t report, porcelain, summary_width);\n \t\tstrbuf_release(&quickref);\n \t}\n }\n \n-static int print_one_push_status(struct ref *ref, const char *dest, int count,\n+static int print_one_push_report(struct ref *ref, const char *dest, int count,\n+\t\t\t\t struct ref_push_report *report,\n \t\t\t\t int porcelain, int summary_width)\n {\n \tif (!count) {\n@@ -543,65 +579,89 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count,\n \tswitch(ref->status) {\n \tcase REF_STATUS_NONE:\n \t\tprint_ref_status('X', \"[no match]\", ref, NULL, NULL,\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NODELETE:\n \t\tprint_ref_status('!', \"[rejected]\", ref, NULL,\n \t\t\t\t \"remote does not support deleting refs\",\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_UPTODATE:\n \t\tprint_ref_status('=', \"[up to date]\", ref,\n-\t\t\t\t ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t ref->peer_ref, NULL,\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NONFASTFORWARD:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"non-fast-forward\", porcelain, summary_width);\n+\t\t\t\t \"non-fast-forward\",\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"already exists\", porcelain, summary_width);\n+\t\t\t\t \"already exists\",\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_FETCH_FIRST:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"fetch first\", porcelain, summary_width);\n+\t\t\t\t \"fetch first\",\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NEEDS_FORCE:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"needs force\", porcelain, summary_width);\n+\t\t\t\t \"needs force\",\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_STALE:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"stale info\", porcelain, summary_width);\n+\t\t\t\t \"stale info\",\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_SHALLOW:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n \t\t\t\t \"new shallow roots not allowed\",\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REMOTE_REJECT:\n \t\tprint_ref_status('!', \"[remote rejected]\", ref,\n \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n-\t\t\t\t ref->remote_status, porcelain, summary_width);\n+\t\t\t\t ref->remote_status,\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_EXPECTING_REPORT:\n \t\tprint_ref_status('!', \"[remote failure]\", ref,\n \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n \t\t\t\t \"remote failed to report status\",\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_ATOMIC_PUSH_FAILED:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"atomic push failed\", porcelain, summary_width);\n+\t\t\t\t \"atomic push failed\",\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_OK:\n-\t\tprint_ok_ref_status(ref, porcelain, summary_width);\n+\t\tprint_ok_ref_status(ref, report, porcelain, summary_width);\n \t\tbreak;\n \t}\n \n \treturn 1;\n }\n \n+static int print_one_push_status(struct ref *ref, const char *dest, int count,\n+\t\t\t\t int porcelain, int summary_width)\n+{\n+\tstruct ref_push_report *report;\n+\tint n = 0;\n+\n+\tif (!ref->report)\n+\t\treturn print_one_push_report(ref, dest, count,\n+\t\t\t\t\t     NULL, porcelain, summary_width);\n+\n+\tfor (report = ref->report; report; report = report->next)\n+\t\tprint_one_push_report(ref, dest, count + n++,\n+\t\t\t\t      report, porcelain, summary_width);\n+\treturn n;\n+}\n+\n static int measure_abbrev(const struct object_id *oid, int sofar)\n {\n \tchar hex[GIT_MAX_HEXSZ + 1];\n-- \n2.26.2.543.ge7de2a6e37\n\n"},{"id":"404341","messageId":"20200824174202.11710-11-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200815171740.6257-1-worldhello.net@gmail.com","subject":"[PATCH v18 10/10] doc: add documentation for the proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-24T17:42:02Z","receivedAt":"2020-08-24T17:42:48Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\n\"git receive-pack\" that accepts requests by \"git push\" learned to\noutsource some of the ref updates to the new \"proc-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/githooks.txt | 62 ++++++++++++++++++++++++++++++++++++++\n 1 file changed, 62 insertions(+)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 81f2a87e88..d66154f606 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -333,6 +333,68 @@ The default 'update' hook, when enabled--and with\n `hooks.allowunannotated` config option unset or set to false--prevents\n unannotated tags to be pushed.\n \n+[[proc-receive]]\n+proc-receive\n+~~~~~~~~~~~~\n+\n+This hook is invoked by linkgit:git-receive-pack[1].  If the server has\n+set the multi-valued config variable `receive.procReceiveRefs`, and the\n+commands sent to 'receive-pack' have matching reference names, these\n+commands will be executed by this hook, instead of by the internal\n+`execute_commands()` function.  This hook is responsible for updating\n+the relevant references and reporting the results back to 'receive-pack'.\n+\n+This hook executes once for the receive operation.  It takes no\n+arguments, but uses a pkt-line format protocol to communicate with\n+'receive-pack' to read commands, push-options and send results.  In the\n+following example for the protocol, the letter 'S' stands for\n+'receive-pack' and the letter 'H' stands for this hook.\n+\n+    # Version and features negotiation.\n+    S: PKT-LINE(version=1\\0push-options atomic...)\n+    S: flush-pkt\n+    H: PKT-LINE(version=1\\0push-options...)\n+    H: flush-pkt\n+\n+    # Send commands from server to the hook.\n+    S: PKT-LINE(<old-oid> <new-oid> <ref>)\n+    S: ... ...\n+    S: flush-pkt\n+    # Send push-options only if the 'push-options' feature is enabled.\n+    S: PKT-LINE(push-option)\n+    S: ... ...\n+    S: flush-pkt\n+\n+    # Receive result from the hook.\n+    # OK, run this command successfully.\n+    H: PKT-LINE(ok <ref>)\n+    # NO, I reject it.\n+    H: PKT-LINE(ng <ref> <reason>)\n+    # Fall through, let 'receive-pack' to execute it.\n+    H: PKT-LINE(ok <ref>)\n+    H: PKT-LINE(option fall-through)\n+    # OK, but has an alternate reference.  The alternate reference name\n+    # and other status can be given in options\n+    H: PKT-LINE(ok <ref>)\n+    H: PKT-LINE(option refname <refname>)\n+    H: PKT-LINE(option old-oid <old-oid>)\n+    H: PKT-LINE(option new-oid <new-oid>)\n+    H: PKT-LINE(option forced-update)\n+    H: ... ...\n+    H: flush-pkt\n+\n+Each command for the 'proc-receive' hook may point to a pseudo-reference\n+and always has a zero-old as its old-oid, while the 'proc-receive' hook\n+may update an alternate reference and the alternate reference may exist\n+already with a non-zero old-oid.  For this case, this hook will use\n+\"option\" directives to report extended attributes for the reference given\n+by the above \"ok/ng\" directive.\n+\n+The report of the commands of this hook should have the same order as\n+the input.  The exit status of the 'proc-receive' hook only determines\n+the success or failure of the group of commands sent to it, unless\n+atomic push is in use.\n+\n [[post-receive]]\n post-receive\n ~~~~~~~~~~~~\n-- \n2.26.2.543.ge7de2a6e37\n\n"},{"id":"404609","messageId":"20200827154551.5966-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200824174202.11710-1-worldhello.net@gmail.com","subject":"[PATCH v19 01/10] transport: not report a non-head push as a branch","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-27T15:45:42Z","receivedAt":"2020-08-27T15:46:05Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing a new reference (not a head or tag), report it as a new\nreference instead of a new branch.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5516-fetch-push.sh | 2 +-\n transport.c           | 9 ++++++---\n 2 files changed, 7 insertions(+), 4 deletions(-)\n\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 36ad20a849..d11382f769 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1039,7 +1039,7 @@ test_force_fetch_tag \"annotated tag\" \"-f -a -m'tag message'\"\n test_expect_success 'push --porcelain' '\n \tmk_empty testrepo &&\n \techo >.git/foo  \"To testrepo\" &&\n-\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new branch]\"  &&\n+\techo >>.git/foo \"*\trefs/heads/master:refs/remotes/origin/master\t[new reference]\"  &&\n \techo >>.git/foo \"Done\" &&\n \tgit push >.git/bar --porcelain  testrepo refs/heads/master:refs/remotes/origin/master &&\n \t(\ndiff --git a/transport.c b/transport.c\nindex 419be0b6ea..84ec80447b 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -500,9 +500,12 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\t\t\t porcelain, summary_width);\n \telse if (is_null_oid(&ref->old_oid))\n \t\tprint_ref_status('*',\n-\t\t\t(starts_with(ref->name, \"refs/tags/\") ? \"[new tag]\" :\n-\t\t\t\"[new branch]\"),\n-\t\t\tref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t  ? \"[new tag]\"\n+\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t     ? \"[new branch]\"\n+\t\t\t\t     : \"[new reference]\")),\n+\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n-- \n2.26.2.543.g44b58e439b\n\n"},{"id":"404610","messageId":"20200827154551.5966-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200824174202.11710-1-worldhello.net@gmail.com","subject":"[PATCH v19 00/10] New proc-receive hook for centralized workflow","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-27T15:45:41Z","receivedAt":"2020-08-27T15:46:05Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\n## Changes since v18\n\n1. This series is based on \"next\" branch, in order to resolve a conflict\n   with commit 95e7c38539 (refspec: make sure stack refspec_item\n   variables are zeroed, 2020-08-14).  See patch 9/10.\n\n2. Fix typos in patch 3/10, 6/10, 7/10.\n\n3. Fix ABNF syntax for capability \"report-status-v2\" in patch 6/10.\n\n4. Add more test cases in patch 7/10 to increase code coverage.\n\n\n## Range diff\n\n 1:  22a9252587 =  1:  96c6d41872 transport: not report a non-head push as a branch\n 2:  5c3fd7eb7a =  2:  861507a38e t5411: add basic test cases for proc-receive hook\n 3:  003a36b71f !  3:  4b6d00a671 receive-pack: add new proc-receive hook\n    @@ Commit message\n         The filter will check the prefix of the reference name, and if the\n         command has a special reference name, the filter will turn a specific\n         field (`run_proc_receive`) on for the command.  Commands with this filed\n    -    turned on will be executed by a new handler (an hook named\n    +    turned on will be executed by a new handler (a hook named\n         \"proc-receive\") instead of the internal `execute_commands` function.\n         We can use this \"proc-receive\" command to create pull requests or send\n         emails for code review.\n     \n         Suggested by Junio, this \"proc-receive\" hook reads the commands,\n         push-options (optional), and send result using a protocol in pkt-line\n    -    format.  In the following example, The letter \"S\" stands for\n    +    format.  In the following example, the letter \"S\" stands for\n         \"receive-pack\" and letter \"H\" stands for the hook.\n     \n             # Version and features negotiation.\n 4:  60b9157a8e =  4:  b3986f98eb receive-pack: feed report options to post-receive\n 5:  4a9b7187af !  5:  882f391b4f New capability \"report-status-v2\" for git-push\n    @@ builtin/receive-pack.c: static void show_ref(const char *path, const struct obje\n      \t\t\tstrbuf_addstr(&cap, \" atomic\");\n      \t\tif (prefer_ofs_delta)\n     @@ builtin/receive-pack.c: static struct command *read_head_info(struct packet_reader *reader,\n    - \t\t\tconst char *feature_list = reader->line + linelen + 1;\n    + \t\t\tint len = 0;\n      \t\t\tif (parse_feature_request(feature_list, \"report-status\"))\n      \t\t\t\treport_status = 1;\n     +\t\t\tif (parse_feature_request(feature_list, \"report-status-v2\"))\n 6:  940acbc406 !  6:  331bfd4697 doc: add document for capability report-status-v2\n    @@ Documentation/technical/pack-protocol.txt: update was successful, or 'ng [refnam\n        error-msg         = 1*(OCTET) ; where not \"ok\"\n      ----\n      \n    -+New capability \"report-status-v2\" extends the protocol by adding new option\n    ++The 'report-status-v2' capability extends the protocol by adding new option\n     +lines in order to support reporting of reference rewritten by the\n    -+\"proc-receive\" hook.  The \"proc-receive\" hook may handle a command for a\n    ++'proc-receive' hook.  The 'proc-receive' hook may handle a command for a\n     +pseudo-reference which may create or update one or more references, and each\n     +reference may have different name, different new-oid, and different old-oid.\n     +\n    @@ Documentation/technical/pack-protocol.txt: update was successful, or 'ng [refnam\n     +  unpack-status     = PKT-LINE(\"unpack\" SP unpack-result)\n     +  unpack-result     = \"ok\" / error-msg\n     +\n    -+  command-status-v2 = command-ok-v2 / command-fail-v2\n    ++  command-status-v2 = command-ok-v2 / command-fail\n     +  command-ok-v2     = command-ok\n     +\t\t      *option-line\n    -+  command-fail-v2   = command-fail\n    -+\t\t      *option-line\n     +\n     +  command-ok        = PKT-LINE(\"ok\" SP refname)\n     +  command-fail      = PKT-LINE(\"ng\" SP refname SP error-msg)\n    @@ Documentation/technical/protocol-capabilities.txt: each reference was updated su\n     +----------------\n     +\n     +Capability 'report-status-v2' extends capability 'report-status' by\n    -+adding new option lines in order to support reference rewritten by\n    -+the\"proc-receive\" hook.  The \"proc-receive\" hook may handle a command\n    ++adding new \"option\" directives in order to support reference rewritten by\n    ++the \"proc-receive\" hook.  The \"proc-receive\" hook may handle a command\n     +for a pseudo-reference which may create or update a reference with\n     +different name, new-oid, and old-oid.  While the capability\n    -+\"report-status\" cannot report for such case.  See pack-protocol.txt\n    ++'report-status' cannot report for such case.  See pack-protocol.txt\n     +for details.\n     +\n      delete-refs\n 7:  974a06e15e !  7:  d5465fc22b receive-pack: new config receive.procReceiveRefs\n    @@ Commit message\n         \"proc-receive\".\n     \n         Optional modifiers can be provided in the beginning of the value to\n    -    filter commands for specific actions: create (a), mofify (m),\n    +    filter commands for specific actions: create (a), modify (m),\n         delete (d). A `!` can be included in the modifiers to negate the\n         reference prefix entry. E.g.:\n     \n             git config --system --add receive.procReceiveRefs ad:refs/heads\n    +        git config --system --add receive.procReceiveRefs !:refs/heads\n     \n         Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n     \n    @@ Documentation/config/receive.txt: receive.hideRefs::\n     +running the hook \"proc-receive\".\n     ++\n     +Optional modifiers can be provided in the beginning of the value to filter\n    -+commands for specific actions: create (a), mofify (m), delete (d).\n    ++commands for specific actions: create (a), modify (m), delete (d).\n     +A `!` can be included in the modifiers to negate the reference prefix entry.\n     +E.g.:\n     ++\n     +\tgit config --system --add receive.procReceiveRefs ad:refs/heads\n    ++\tgit config --system --add receive.procReceiveRefs !:refs/heads\n     +\n      receive.updateServerInfo::\n      \tIf set to true, git-receive-pack will run git-update-server-info\n    @@ t/t5411/test-0041-process-all-refs--porcelain.sh (new)\n     +\t)\n     +'\n     \n    - ## t/t5411/test-0050-proc-receive-refs.sh (new) ##\n    + ## t/t5411/test-0050-proc-receive-refs-with-modifiers.sh (new) ##\n     @@\n     +test_expect_success \"config receive.procReceiveRefs with modifiers ($PROTOCOL)\" '\n     +\t(\n    @@ t/t5411/test-0050-proc-receive-refs.sh (new)\n     +# Refs of upstream : master(A)\n     +# Refs of workbench: master(A)  tags/v123\n     +test_expect_success \"setup upstream: create tags/v123 ($PROTOCOL)\" '\n    ++\tgit -C \"$upstream\" update-ref refs/heads/topic $A &&\n     +\tgit -C \"$upstream\" update-ref refs/tags/v123 $TAG &&\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n    ++\t<COMMIT-A> refs/heads/topic\n     +\t<TAG-v123> refs/tags/v123\n     +\tEOF\n     +\ttest_cmp expect actual\n    @@ t/t5411/test-0050-proc-receive-refs.sh (new)\n     +\tEOF\n     +'\n     +\n    -+# Refs of upstream : master(A)  tags/v123\n    -+# Refs of workbench: master(A)  tags/v123\n    -+# git push         : NULL       NULL       next(A)\n    ++# Refs of upstream : master(A)  topic(A)  tags/v123\n    ++# Refs of workbench: master(A)            tags/v123\n    ++# git push         : NULL       topic(B)  NULL       next(A)\n     +test_expect_success \"proc-receive: create/delete branch, and delete tag ($PROTOCOL)\" '\n     +\tgit -C workbench push origin \\\n     +\t\t:refs/heads/master \\\n    ++\t\t$B:refs/heads/topic \\\n     +\t\t$A:refs/heads/next \\\n     +\t\t:refs/tags/v123 >out 2>&1 &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\tremote: # pre-receive hook\n     +\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/master\n    ++\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/topic\n     +\tremote: pre-receive< <TAG-v123> <ZERO-OID> refs/tags/v123\n     +\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n     +\tremote: # proc-receive hook\n    @@ t/t5411/test-0050-proc-receive-refs.sh (new)\n     +\tremote: proc-receive> option new-oid <COMMIT-A>\n     +\tremote: # post-receive hook\n     +\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/pull/123/head\n    ++\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/topic\n     +\tremote: post-receive< <TAG-v123> <ZERO-OID> refs/tags/v123\n     +\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/124/head\n     +\tTo <URL/of/upstream.git>\n     +\t - [deleted] refs/pull/123/head\n    ++\t <OID-A>..<OID-B> <COMMIT-B> -> topic\n     +\t - [deleted] v123\n     +\t * [new reference] <COMMIT-A> -> refs/pull/124/head\n     +\tEOF\n    @@ t/t5411/test-0050-proc-receive-refs.sh (new)\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n     +\t<COMMIT-A> refs/heads/master\n    ++\t<COMMIT-B> refs/heads/topic\n     +\tEOF\n     +\ttest_cmp expect actual\n     +'\n 8:  5b588f7926 =  8:  cd38e2ddf5 t5411: test updates of remote-tracking branches\n 9:  7c707984e3 !  9:  342784222f transport: parse report options for tracking refs\n    @@ transport.c: int transport_refs_pushed(struct ref *ref)\n      }\n      \n     -void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose)\n    -+static void update_tracking_ref(struct remote *remote, char *refname,\n    -+\t\t\t\tstruct object_id *new_oid, int deletion,\n    -+\t\t\t\tint verbose)\n    ++static void update_one_tracking_ref(struct remote *remote, char *refname,\n    ++\t\t\t\t    struct object_id *new_oid, int deletion,\n    ++\t\t\t\t    int verbose)\n      {\n      \tstruct refspec_item rs;\n      \n     -\tif (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE)\n     -\t\treturn;\n     -\n    + \tmemset(&rs, 0, sizeof(rs));\n     -\trs.src = ref->name;\n     +\trs.src = refname;\n      \trs.dst = NULL;\n    --\n    + \n      \tif (!remote_find_tracking(remote, &rs)) {\n      \t\tif (verbose)\n      \t\t\tfprintf(stderr, \"updating local tracking ref '%s'\\n\", rs.dst);\n    @@ transport.c: int transport_refs_pushed(struct ref *ref)\n      \t\t\tdelete_ref(NULL, rs.dst, NULL, 0);\n     -\t\t} else\n     -\t\t\tupdate_ref(\"update by push\", rs.dst, &ref->new_oid,\n    --\t\t\t\t   NULL, 0, 0);\n     +\t\telse\n     +\t\t\tupdate_ref(\"update by push\", rs.dst, new_oid,\n    -+\t\t\t\tNULL, 0, 0);\n    + \t\t\t\t   NULL, 0, 0);\n      \t\tfree(rs.dst);\n      \t}\n      }\n    @@ transport.c: int transport_refs_pushed(struct ref *ref)\n     +\n     +\treport = ref->report;\n     +\tif (!report)\n    -+\t\tupdate_tracking_ref(remote, ref->name, &ref->new_oid,\n    -+\t\t\t\t    ref->deletion, verbose);\n    ++\t\tupdate_one_tracking_ref(remote, ref->name, &ref->new_oid,\n    ++\t\t\t\t\tref->deletion, verbose);\n     +\telse\n     +\t\tfor (; report; report = report->next) {\n     +\t\t\trefname = report->ref_name ? (char *)report->ref_name : ref->name;\n     +\t\t\tnew_oid = report->new_oid ? report->new_oid : &ref->new_oid;\n    -+\t\t\tupdate_tracking_ref(remote, refname, new_oid,\n    -+\t\t\t\t\t    is_null_oid(new_oid), verbose);\n    ++\t\t\tupdate_one_tracking_ref(remote, refname, new_oid,\n    ++\t\t\t\t\t\tis_null_oid(new_oid), verbose);\n     +\t\t}\n     +}\n     +\n10:  e66c7c3f51 ! 10:  a7268305e3 doc: add documentation for the proc-receive hook\n    @@ Documentation/githooks.txt: The default 'update' hook, when enabled--and with\n     +    H: PKT-LINE(ok <ref>)\n     +    H: PKT-LINE(option fall-through)\n     +    # OK, but has an alternate reference.  The alternate reference name\n    -+    # and other status can be given in options\n    ++    # and other status can be given in option directives.\n     +    H: PKT-LINE(ok <ref>)\n     +    H: PKT-LINE(option refname <refname>)\n     +    H: PKT-LINE(option old-oid <old-oid>)\n    @@ Documentation/githooks.txt: The default 'update' hook, when enabled--and with\n     +may update an alternate reference and the alternate reference may exist\n     +already with a non-zero old-oid.  For this case, this hook will use\n     +\"option\" directives to report extended attributes for the reference given\n    -+by the above \"ok/ng\" directive.\n    ++by the leading \"ok\" directive.\n     +\n     +The report of the commands of this hook should have the same order as\n     +the input.  The exit status of the 'proc-receive' hook only determines\n\n---\n\nJiang Xin (10):\n  transport: not report a non-head push as a branch\n  t5411: add basic test cases for proc-receive hook\n  receive-pack: add new proc-receive hook\n  receive-pack: feed report options to post-receive\n  New capability \"report-status-v2\" for git-push\n  doc: add document for capability report-status-v2\n  receive-pack: new config receive.procReceiveRefs\n  t5411: test updates of remote-tracking branches\n  transport: parse report options for tracking refs\n  doc: add documentation for the proc-receive hook\n\n Documentation/config/receive.txt              |  22 +\n Documentation/githooks.txt                    |  62 +++\n Documentation/technical/pack-protocol.txt     |  41 +-\n .../technical/protocol-capabilities.txt       |  17 +-\n Makefile                                      |   1 +\n builtin/receive-pack.c                        | 472 +++++++++++++++++-\n builtin/send-pack.c                           |  19 +\n remote.h                                      |   9 +\n send-pack.c                                   | 101 +++-\n t/helper/test-proc-receive.c                  | 176 +++++++\n t/helper/test-tool.c                          |   1 +\n t/helper/test-tool.h                          |   1 +\n t/t5411-proc-receive-hook.sh                  | 117 +++++\n t/t5411/common-functions.sh                   |  56 +++\n t/t5411/once-0010-report-status-v1.sh         |  94 ++++\n t/t5411/test-0000-standard-git-push.sh        | 143 ++++++\n .../test-0001-standard-git-push--porcelain.sh | 147 ++++++\n t/t5411/test-0002-pre-receive-declined.sh     |  33 ++\n ...st-0003-pre-receive-declined--porcelain.sh |  34 ++\n t/t5411/test-0010-proc-receive-settings.sh    |   7 +\n t/t5411/test-0011-no-hook-error.sh            |  64 +++\n t/t5411/test-0012-no-hook-error--porcelain.sh |  66 +++\n t/t5411/test-0013-bad-protocol.sh             | 217 ++++++++\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 160 ++++++\n t/t5411/test-0020-report-ng.sh                |  67 +++\n t/t5411/test-0021-report-ng--porcelain.sh     |  69 +++\n t/t5411/test-0022-report-unexpect-ref.sh      |  45 ++\n ...est-0023-report-unexpect-ref--porcelain.sh |  46 ++\n t/t5411/test-0024-report-unknown-ref.sh       |  34 ++\n ...test-0025-report-unknown-ref--porcelain.sh |  35 ++\n t/t5411/test-0026-push-options.sh             |  79 +++\n t/t5411/test-0027-push-options--porcelain.sh  |  82 +++\n t/t5411/test-0030-report-ok.sh                |  35 ++\n t/t5411/test-0031-report-ok--porcelain.sh     |  36 ++\n t/t5411/test-0032-report-with-options.sh      | 256 ++++++++++\n ...est-0033-report-with-options--porcelain.sh | 265 ++++++++++\n t/t5411/test-0034-report-ft.sh                |  44 ++\n t/t5411/test-0035-report-ft--porcelain.sh     |  45 ++\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 227 +++++++++\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 172 +++++++\n t/t5411/test-0038-report-mixed-refs.sh        |  89 ++++\n .../test-0039-report-mixed-refs--porcelain.sh |  91 ++++\n t/t5411/test-0040-process-all-refs.sh         | 113 +++++\n .../test-0041-process-all-refs--porcelain.sh  | 114 +++++\n ...t-0050-proc-receive-refs-with-modifiers.sh | 135 +++++\n t/t5516-fetch-push.sh                         |   2 +-\n transport-helper.c                            | 128 +++--\n transport.c                                   | 156 ++++--\n 48 files changed, 4325 insertions(+), 100 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/once-0010-report-status-v1.sh\n create mode 100644 t/t5411/test-0000-standard-git-push.sh\n create mode 100644 t/t5411/test-0001-standard-git-push--porcelain.sh\n create mode 100644 t/t5411/test-0002-pre-receive-declined.sh\n create mode 100644 t/t5411/test-0003-pre-receive-declined--porcelain.sh\n create mode 100644 t/t5411/test-0010-proc-receive-settings.sh\n create mode 100644 t/t5411/test-0011-no-hook-error.sh\n create mode 100644 t/t5411/test-0012-no-hook-error--porcelain.sh\n create mode 100644 t/t5411/test-0013-bad-protocol.sh\n create mode 100644 t/t5411/test-0014-bad-protocol--porcelain.sh\n create mode 100644 t/t5411/test-0020-report-ng.sh\n create mode 100644 t/t5411/test-0021-report-ng--porcelain.sh\n create mode 100644 t/t5411/test-0022-report-unexpect-ref.sh\n create mode 100644 t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n create mode 100644 t/t5411/test-0024-report-unknown-ref.sh\n create mode 100644 t/t5411/test-0025-report-unknown-ref--porcelain.sh\n create mode 100644 t/t5411/test-0026-push-options.sh\n create mode 100644 t/t5411/test-0027-push-options--porcelain.sh\n create mode 100644 t/t5411/test-0030-report-ok.sh\n create mode 100644 t/t5411/test-0031-report-ok--porcelain.sh\n create mode 100644 t/t5411/test-0032-report-with-options.sh\n create mode 100644 t/t5411/test-0033-report-with-options--porcelain.sh\n create mode 100644 t/t5411/test-0034-report-ft.sh\n create mode 100644 t/t5411/test-0035-report-ft--porcelain.sh\n create mode 100644 t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n create mode 100644 t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n create mode 100644 t/t5411/test-0038-report-mixed-refs.sh\n create mode 100644 t/t5411/test-0039-report-mixed-refs--porcelain.sh\n create mode 100644 t/t5411/test-0040-process-all-refs.sh\n create mode 100644 t/t5411/test-0041-process-all-refs--porcelain.sh\n create mode 100644 t/t5411/test-0050-proc-receive-refs-with-modifiers.sh\n\n-- \n2.26.2.543.g44b58e439b\n\n"},{"id":"404611","messageId":"20200827154551.5966-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200824174202.11710-1-worldhello.net@gmail.com","subject":"[PATCH v19 02/10] t5411: add basic test cases for proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-27T15:45:43Z","receivedAt":"2020-08-27T15:46:08Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nTopic \"proc-receive-hook\" will change the workflow and output of\ngit-push. Add some basic test cases in t5411 before introducing the new\ntopic.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411-proc-receive-hook.sh                  | 108 +++++++++++++\n t/t5411/common-functions.sh                   |  56 +++++++\n t/t5411/test-0000-standard-git-push.sh        | 143 +++++++++++++++++\n .../test-0001-standard-git-push--porcelain.sh | 147 ++++++++++++++++++\n t/t5411/test-0002-pre-receive-declined.sh     |  33 ++++\n ...st-0003-pre-receive-declined--porcelain.sh |  34 ++++\n 6 files changed, 521 insertions(+)\n create mode 100755 t/t5411-proc-receive-hook.sh\n create mode 100644 t/t5411/common-functions.sh\n create mode 100644 t/t5411/test-0000-standard-git-push.sh\n create mode 100644 t/t5411/test-0001-standard-git-push--porcelain.sh\n create mode 100644 t/t5411/test-0002-pre-receive-declined.sh\n create mode 100644 t/t5411/test-0003-pre-receive-declined--porcelain.sh\n\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nnew file mode 100755\nindex 0000000000..3a684353a8\n--- /dev/null\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -0,0 +1,108 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2020 Jiang Xin\n+#\n+\n+test_description='Test proc-receive hook'\n+\n+. ./test-lib.sh\n+\n+. \"$TEST_DIRECTORY\"/t5411/common-functions.sh\n+\n+setup_upstream_and_workbench () {\n+\t# Refs of upstream : master(A)\n+\t# Refs of workbench: master(A)  tags/v123\n+\ttest_expect_success \"setup upstream and workbench\" '\n+\t\trm -rf upstream.git &&\n+\t\trm -rf workbench &&\n+\t\tgit init --bare upstream.git &&\n+\t\tgit init workbench &&\n+\t\tcreate_commits_in workbench A B &&\n+\t\t(\n+\t\t\tcd workbench &&\n+\t\t\t# Try to make a stable fixed width for abbreviated commit ID,\n+\t\t\t# this fixed-width oid will be replaced with \"<OID>\".\n+\t\t\tgit config core.abbrev 7 &&\n+\t\t\tgit tag -m \"v123\" v123 $A &&\n+\t\t\tgit remote add origin ../upstream.git &&\n+\t\t\tgit push origin master &&\n+\t\t\tgit update-ref refs/heads/master $A $B &&\n+\t\t\tgit -C ../upstream.git update-ref \\\n+\t\t\t\trefs/heads/master $A $B\n+\t\t) &&\n+\t\tTAG=$(git -C workbench rev-parse v123) &&\n+\n+\t\t# setup pre-receive hook\n+\t\twrite_script upstream.git/hooks/pre-receive <<-\\EOF &&\n+\t\texec >&2\n+\t\techo \"# pre-receive hook\"\n+\t\twhile read old new ref\n+\t\tdo\n+\t\t\techo \"pre-receive< $old $new $ref\"\n+\t\tdone\n+\t\tEOF\n+\n+\t\t# setup post-receive hook\n+\t\twrite_script upstream.git/hooks/post-receive <<-\\EOF &&\n+\t\texec >&2\n+\t\techo \"# post-receive hook\"\n+\t\twhile read old new ref\n+\t\tdo\n+\t\t\techo \"post-receive< $old $new $ref\"\n+\t\tdone\n+\t\tEOF\n+\n+\t\tupstream=upstream.git\n+\t'\n+}\n+\n+run_proc_receive_hook_test() {\n+\tcase $1 in\n+\thttp)\n+\t\tPROTOCOL=\"HTTP protocol\"\n+\t\tURL_PREFIX=\"http://.*\"\n+\t\t;;\n+\tlocal)\n+\t\tPROTOCOL=\"builtin protocol\"\n+\t\tURL_PREFIX=\"\\.\\.\"\n+\t\t;;\n+\tesac\n+\n+\t# Include test cases for both file and HTTP protocol\n+\tfor t in  \"$TEST_DIRECTORY\"/t5411/test-*.sh\n+\tdo\n+\t\t. \"$t\"\n+\tdone\n+}\n+\n+# Initialize the upstream repository and local workbench.\n+setup_upstream_and_workbench\n+\n+# Run test cases for 'proc-receive' hook on local file protocol.\n+run_proc_receive_hook_test local\n+\n+ROOT_PATH=\"$PWD\"\n+. \"$TEST_DIRECTORY\"/lib-gpg.sh\n+. \"$TEST_DIRECTORY\"/lib-httpd.sh\n+. \"$TEST_DIRECTORY\"/lib-terminal.sh\n+start_httpd\n+\n+# Re-initialize the upstream repository and local workbench.\n+setup_upstream_and_workbench\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup for HTTP protocol\" '\n+\tgit -C upstream.git config http.receivepack true &&\n+\tupstream=\"$HTTPD_DOCUMENT_ROOT_PATH/upstream.git\" &&\n+\tmv upstream.git \"$upstream\" &&\n+\tgit -C workbench remote set-url origin \"$HTTPD_URL/auth-push/smart/upstream.git\" &&\n+\tset_askpass user@host pass@host\n+'\n+\n+setup_askpass_helper\n+\n+# Run test cases for 'proc-receive' hook on HTTP protocol.\n+run_proc_receive_hook_test http\n+\n+test_done\ndiff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\nnew file mode 100644\nindex 0000000000..6580bebd8e\n--- /dev/null\n+++ b/t/t5411/common-functions.sh\n@@ -0,0 +1,56 @@\n+# Create commits in <repo> and assign each commit's oid to shell variables\n+# given in the arguments (A, B, and C). E.g.:\n+#\n+#     create_commits_in <repo> A B C\n+#\n+# NOTE: Never calling this function from a subshell since variable\n+# assignments will disappear when subshell exits.\n+create_commits_in () {\n+\trepo=\"$1\" &&\n+\tif ! parent=$(git -C \"$repo\" rev-parse HEAD^{} --)\n+\tthen\n+\t\tparent=\n+\tfi &&\n+\tT=$(git -C \"$repo\" write-tree) &&\n+\tshift &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\tname=$1 &&\n+\t\ttest_tick &&\n+\t\tif test -z \"$parent\"\n+\t\tthen\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree $T)\n+\t\telse\n+\t\t\toid=$(echo $name | git -C \"$repo\" commit-tree -p $parent $T)\n+\t\tfi &&\n+\t\teval $name=$oid &&\n+\t\tparent=$oid &&\n+\t\tshift ||\n+\t\treturn 1\n+\tdone &&\n+\tgit -C \"$repo\" update-ref refs/heads/master $oid\n+}\n+\n+# Format the output of git-push, git-show-ref and other commands to make a\n+# user-friendly and stable text.  We can easily prepare the expect text\n+# without having to worry about future changes of the commit ID and spaces\n+# of the output.  Single quotes are replaced with double quotes, because\n+# it is boring to prepare unquoted single quotes in expect text.  We also\n+# remove some locale error messages, which break test if we turn on\n+# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n+# on plumbing commands.\n+make_user_friendly_and_stable_output () {\n+\tsed \\\n+\t\t-e \"s/  *\\$//\" \\\n+\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/'/\\\"/g\" \\\n+\t\t-e \"s/\t/    /g\" \\\n+\t\t-e \"s/$A/<COMMIT-A>/g\" \\\n+\t\t-e \"s/$B/<COMMIT-B>/g\" \\\n+\t\t-e \"s/$TAG/<TAG-v123>/g\" \\\n+\t\t-e \"s/$ZERO_OID/<ZERO-OID>/g\" \\\n+\t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n+\t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\" \\\n+\t\t-e \"s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#\" \\\n+\t\t-e \"/^error: / d\"\n+}\ndiff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh\nnew file mode 100644\nindex 0000000000..e206587348\n--- /dev/null\n+++ b/t/t5411/test-0000-standard-git-push.sh\n@@ -0,0 +1,143 @@\n+# Refs of upstream : master(A)  \n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t * [new branch] HEAD -> next\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push --atomic: master(A)  next(B)\n+test_expect_success \"git-push --atomic ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\tmaster \\\n+\t\t$B:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out |\n+\t\tsed -n \\\n+\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n+\t\t\t-e \"/^ ! / { s/   */ /g; p; }\" \\\n+\t\t\t>actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [rejected] master -> master (non-fast-forward)\n+\t ! [rejected] <COMMIT-B> -> next (atomic push failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push         : master(A)  next(B)\n+test_expect_success \"non-fast-forward git-push ($PROTOCOL)\" '\n+\ttest_must_fail git \\\n+\t\t-C workbench \\\n+\t\t-c advice.pushUpdateRejected=false \\\n+\t\tpush origin \\\n+\t\tmaster \\\n+\t\t$B:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> next\n+\t ! [rejected] master -> master (non-fast-forward)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-B> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(B)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"git-push -f ($PROTOCOL)\" '\n+\tgit -C workbench push -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tmaster \\\n+\t\tmaster:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-B> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-B> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo <URL/of/upstream.git>\n+\t + <OID-B>...<OID-A> master -> master (forced update)\n+\t - [deleted] next\n+\t * [new tag] v123 -> v123\n+\t * [new reference] master -> refs/review/master/topic\n+\t * [new branch] HEAD -> a/b/c\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\ndiff --git a/t/t5411/test-0001-standard-git-push--porcelain.sh b/t/t5411/test-0001-standard-git-push--porcelain.sh\nnew file mode 100644\nindex 0000000000..48f6fcc846\n--- /dev/null\n+++ b/t/t5411/test-0001-standard-git-push--porcelain.sh\n@@ -0,0 +1,147 @@\n+# Refs of upstream : master(A)  \n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push --atomic: master(A)  next(B)\n+test_expect_success \"git-push --atomic ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --atomic --porcelain origin \\\n+\t\tmaster \\\n+\t\t$B:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out |\n+\t\tsed -n \\\n+\t\t\t-e \"s/^# GETTEXT POISON #//\" \\\n+\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n+\t\t\t-e \"/^! / { s/   */ /g; p; }\" \\\n+\t\t\t>actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t! refs/heads/master:refs/heads/master [rejected] (non-fast-forward)\n+\t! <COMMIT-B>:refs/heads/next [rejected] (atomic push failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(A)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push         : master(A)  next(B)\n+test_expect_success \"non-fast-forward git-push ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git \\\n+\t\t-C workbench \\\n+\t\t-c advice.pushUpdateRejected=false \\\n+\t\tpush --porcelain origin \\\n+\t\tmaster \\\n+\t\t$B:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/next    <OID-A>..<OID-B>\n+\t!    refs/heads/master:refs/heads/master    [rejected] (non-fast-forward)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\t<COMMIT-B> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)  next(B)\n+# Refs of workbench: master(A)           tags/v123\n+# git-push -f      : master(A)  NULL     tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+test_expect_success \"git-push -f ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain -f origin \\\n+\t\trefs/tags/v123 \\\n+\t\t:refs/heads/next \\\n+\t\tmaster \\\n+\t\tmaster:refs/review/master/topic \\\n+\t\tHEAD:refs/heads/a/b/c \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <COMMIT-B> <ZERO-OID> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-B> <ZERO-OID> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c\n+\tTo <URL/of/upstream.git>\n+\t+    refs/heads/master:refs/heads/master    <OID-B>...<OID-A> (forced update)\n+\t-    :refs/heads/next    [deleted]\n+\t*    refs/tags/v123:refs/tags/v123    [new tag]\n+\t*    refs/heads/master:refs/review/master/topic    [new reference]\n+\t*    HEAD:refs/heads/a/b/c    [new branch]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/a/b/c\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/review/master/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)  tags/v123  refs/review/master/topic(A)  a/b/c(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/review/master/topic &&\n+\t\tgit update-ref -d refs/tags/v123 &&\n+\t\tgit update-ref -d refs/heads/a/b/c\n+\t)\n+'\ndiff --git a/t/t5411/test-0002-pre-receive-declined.sh b/t/t5411/test-0002-pre-receive-declined.sh\nnew file mode 100644\nindex 0000000000..c246f7e68e\n--- /dev/null\n+++ b/t/t5411/test-0002-pre-receive-declined.sh\n@@ -0,0 +1,33 @@\n+test_expect_success \"setup pre-receive hook ($PROTOCOL)\" '\n+\tmv \"$upstream/hooks/pre-receive\" \"$upstream/hooks/pre-receive.ok\" &&\n+\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n+\texit 1\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push is declined ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] <COMMIT-B> -> master (pre-receive hook declined)\n+\t ! [remote rejected] HEAD -> next (pre-receive hook declined)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tmv \"$upstream/hooks/pre-receive.ok\" \"$upstream/hooks/pre-receive\"\n+'\ndiff --git a/t/t5411/test-0003-pre-receive-declined--porcelain.sh b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\nnew file mode 100644\nindex 0000000000..b14894de81\n--- /dev/null\n+++ b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\n@@ -0,0 +1,34 @@\n+test_expect_success \"setup pre-receive hook ($PROTOCOL/porcelain)\" '\n+\tmv \"$upstream/hooks/pre-receive\" \"$upstream/hooks/pre-receive.ok\" &&\n+\twrite_script \"$upstream/hooks/pre-receive\" <<-EOF\n+\texit 1\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git-push         : master(B)             next(A)\n+test_expect_success \"git-push is declined ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/next \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    <COMMIT-B>:refs/heads/master    [remote rejected] (pre-receive hook declined)\n+\t!    HEAD:refs/heads/next    [remote rejected] (pre-receive hook declined)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tmv \"$upstream/hooks/pre-receive.ok\" \"$upstream/hooks/pre-receive\"\n+'\n-- \n2.26.2.543.g44b58e439b\n\n"},{"id":"404612","messageId":"20200827154551.5966-5-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200824174202.11710-1-worldhello.net@gmail.com","subject":"[PATCH v19 04/10] receive-pack: feed report options to post-receive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-27T15:45:45Z","receivedAt":"2020-08-27T15:46:12Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen commands are fed to the \"post-receive\" hook, report options will\nbe parsed and the real old-oid, new-oid, reference name will feed to\nthe \"post-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c                        | 30 ++++++++++++++++---\n t/t5411/test-0032-report-with-options.sh      | 14 ++++-----\n ...est-0033-report-with-options--porcelain.sh | 14 ++++-----\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 11 +++++--\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 11 +++++--\n t/t5411/test-0038-report-mixed-refs.sh        |  2 +-\n .../test-0039-report-mixed-refs--porcelain.sh |  2 +-\n 7 files changed, 58 insertions(+), 26 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex d4614f6f67..0390f2dc97 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -696,6 +696,7 @@ static void prepare_push_cert_sha1(struct child_process *proc)\n \n struct receive_hook_feed_state {\n \tstruct command *cmd;\n+\tstruct ref_push_report *report;\n \tint skip_broken;\n \tstruct strbuf buf;\n \tconst struct string_list *push_options;\n@@ -783,11 +784,31 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n \t\tcmd = cmd->next;\n \tif (!cmd)\n \t\treturn -1; /* EOF */\n+\tif (!bufp)\n+\t\treturn 0; /* OK, can feed something. */\n \tstrbuf_reset(&state->buf);\n-\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n-\t\t    oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),\n-\t\t    cmd->ref_name);\n-\tstate->cmd = cmd->next;\n+\tif (!state->report)\n+\t\tstate->report = cmd->report;\n+\tif (state->report) {\n+\t\tstruct object_id *old_oid;\n+\t\tstruct object_id *new_oid;\n+\t\tconst char *ref_name;\n+\n+\t\told_oid = state->report->old_oid ? state->report->old_oid : &cmd->old_oid;\n+\t\tnew_oid = state->report->new_oid ? state->report->new_oid : &cmd->new_oid;\n+\t\tref_name = state->report->ref_name ? state->report->ref_name : cmd->ref_name;\n+\t\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n+\t\t\t    oid_to_hex(old_oid), oid_to_hex(new_oid),\n+\t\t\t    ref_name);\n+\t\tstate->report = state->report->next;\n+\t\tif (!state->report)\n+\t\t\tstate->cmd = cmd->next;\n+\t} else {\n+\t\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n+\t\t\t    oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),\n+\t\t\t    cmd->ref_name);\n+\t\tstate->cmd = cmd->next;\n+\t}\n \tif (bufp) {\n \t\t*bufp = state->buf.buf;\n \t\t*sizep = state->buf.len;\n@@ -806,6 +827,7 @@ static int run_receive_hook(struct command *commands,\n \tstrbuf_init(&state.buf, 0);\n \tstate.cmd = commands;\n \tstate.skip_broken = skip_broken;\n+\tstate.report = NULL;\n \tif (feed_receive_hook(&state, NULL, NULL))\n \t\treturn 0;\n \tstate.cmd = commands;\ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nindex a743aa8018..c559c207fa 100644\n--- a/t/t5411/test-0032-report-with-options.sh\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -54,7 +54,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL)\" '\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/for/master/topic\n \tEOF\n@@ -87,7 +87,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/for/master/topic\n \tEOF\n@@ -121,7 +121,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL)\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/for/master/topic\n \tEOF\n@@ -153,7 +153,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL)\" '\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/for/master/topic\n \tEOF\n@@ -187,7 +187,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL)\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/for/master/topic\n \tEOF\n@@ -237,9 +237,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n \tremote: proc-receive> option forced-update\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/for/next/topic\n \t * [new reference] HEAD -> refs/for/a/b/c/topic\ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nindex 439b97b06e..ea9312234e 100644\n--- a/t/t5411/test-0033-report-with-options--porcelain.sh\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -55,7 +55,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL/porcelain)\"\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/for/master/topic    [new reference]\n \tDone\n@@ -90,7 +90,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/for/master/topic    [new reference]\n \tDone\n@@ -125,7 +125,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL/\n \tremote: proc-receive> option refname refs/pull/123/head\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/for/master/topic    [new reference]\n \tDone\n@@ -158,7 +158,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL/porcelain)\"\n \tremote: proc-receive> ok refs/for/master/topic\n \tremote: proc-receive> option old-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/for/master/topic    [new reference]\n \tDone\n@@ -193,7 +193,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL/\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/for/master/topic    [new reference]\n \tDone\n@@ -245,9 +245,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porc\n \tremote: proc-receive> option forced-update\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/for/next/topic    [new reference]\n \t*    HEAD:refs/for/a/b/c/topic    [new reference]\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex 12acf9ea1f..67cc4a8a4c 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -41,7 +41,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/for/master/topic\n \tEOF\n@@ -99,7 +101,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/for/master/topic\n \tEOF\n@@ -145,7 +149,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2\n \tTo <URL/of/upstream.git>\n \t * [new reference] HEAD -> refs/for/master/topic\n \tEOF\ndiff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\nindex f4a2c56e14..1dc8551bc6 100644\n--- a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n@@ -41,7 +41,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/for/master/topic    [new reference]\n \tDone\n@@ -100,7 +102,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: proc-receive> option new-oid <COMMIT-A>\n \tremote: proc-receive> option forced-update\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/for/master/topic    [new reference]\n \tDone\n@@ -147,7 +151,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porc\n \tremote: proc-receive> option old-oid <COMMIT-A>\n \tremote: proc-receive> option new-oid <COMMIT-B>\n \tremote: # post-receive hook\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/for/master/topic    [new reference]\n \tDone\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nindex 1dd6a97a97..bfc8d586d6 100644\n--- a/t/t5411/test-0038-report-mixed-refs.sh\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -53,7 +53,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t <OID-A>..<OID-B> <COMMIT-B> -> master\n \t * [new branch] HEAD -> bar\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nindex 32ebf63dcf..5d021a4837 100644\n--- a/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -53,7 +53,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcel\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n-\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n \t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n \t*    HEAD:refs/heads/bar    [new branch]\n-- \n2.26.2.543.g44b58e439b\n\n"},{"id":"404613","messageId":"20200827154551.5966-8-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200824174202.11710-1-worldhello.net@gmail.com","subject":"[PATCH v19 07/10] receive-pack: new config receive.procReceiveRefs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-27T15:45:48Z","receivedAt":"2020-08-27T15:46:14Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd a new multi-valued config variable \"receive.procReceiveRefs\"\nfor `receive-pack` command, like the follows:\n\n    git config --system --add receive.procReceiveRefs refs/for\n    git config --system --add receive.procReceiveRefs refs/drafts\n\nIf the specific prefix strings given by the config variables match the\nreference names of the commands which are sent from git client to\n`receive-pack`, these commands will be executed by an external hook\n(named \"proc-receive\"), instead of the internal `execute_commands`\nfunction.\n\nFor example, if it is set to \"refs/for\", pushing to a reference such as\n\"refs/for/master\" will not create or update reference \"refs/for/master\",\nbut may create or update a pull request directly by running the hook\n\"proc-receive\".\n\nOptional modifiers can be provided in the beginning of the value to\nfilter commands for specific actions: create (a), modify (m),\ndelete (d). A `!` can be included in the modifiers to negate the\nreference prefix entry. E.g.:\n\n    git config --system --add receive.procReceiveRefs ad:refs/heads\n    git config --system --add receive.procReceiveRefs !:refs/heads\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/config/receive.txt              |  22 +++\n builtin/receive-pack.c                        | 106 +++++++++++++-\n t/t5411/once-0010-report-status-v1.sh         |   4 +\n t/t5411/test-0010-proc-receive-settings.sh    |   7 +\n t/t5411/test-0040-process-all-refs.sh         | 113 +++++++++++++++\n .../test-0041-process-all-refs--porcelain.sh  | 114 +++++++++++++++\n ...t-0050-proc-receive-refs-with-modifiers.sh | 135 ++++++++++++++++++\n 7 files changed, 494 insertions(+), 7 deletions(-)\n create mode 100644 t/t5411/test-0010-proc-receive-settings.sh\n create mode 100644 t/t5411/test-0040-process-all-refs.sh\n create mode 100644 t/t5411/test-0041-process-all-refs--porcelain.sh\n create mode 100644 t/t5411/test-0050-proc-receive-refs-with-modifiers.sh\n\ndiff --git a/Documentation/config/receive.txt b/Documentation/config/receive.txt\nindex 65f78aac37..85d5b5a3d2 100644\n--- a/Documentation/config/receive.txt\n+++ b/Documentation/config/receive.txt\n@@ -114,6 +114,28 @@ receive.hideRefs::\n \tAn attempt to update or delete a hidden ref by `git push` is\n \trejected.\n \n+receive.procReceiveRefs::\n+\tThis is a multi-valued variable that defines reference prefixes\n+\tto match the commands in `receive-pack`.  Commands matching the\n+\tprefixes will be executed by an external hook \"proc-receive\",\n+\tinstead of the internal `execute_commands` function.  If this\n+\tvariable is not defined, the \"proc-receive\" hook will never be\n+\tused, and all commands will be executed by the internal\n+\t`execute_commands` function.\n++\n+For example, if this variable is set to \"refs/for\", pushing to reference\n+such as \"refs/for/master\" will not create or update a reference named\n+\"refs/for/master\", but may create or update a pull request directly by\n+running the hook \"proc-receive\".\n++\n+Optional modifiers can be provided in the beginning of the value to filter\n+commands for specific actions: create (a), modify (m), delete (d).\n+A `!` can be included in the modifiers to negate the reference prefix entry.\n+E.g.:\n++\n+\tgit config --system --add receive.procReceiveRefs ad:refs/heads\n+\tgit config --system --add receive.procReceiveRefs !:refs/heads\n+\n receive.updateServerInfo::\n \tIf set to true, git-receive-pack will run git-update-server-info\n \tafter receiving data from git-push and updating refs.\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex e3dfb17ad4..bb9909c52e 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -98,6 +98,17 @@ static int keepalive_in_sec = 5;\n \n static struct tmp_objdir *tmp_objdir;\n \n+static struct proc_receive_ref {\n+\tunsigned int want_add:1,\n+\t\t     want_delete:1,\n+\t\t     want_modify:1,\n+\t\t     negative_ref:1;\n+\tchar *ref_prefix;\n+\tstruct proc_receive_ref *next;\n+} *proc_receive_ref;\n+\n+static void proc_receive_ref_append(const char *prefix);\n+\n static enum deny_action parse_deny_action(const char *var, const char *value)\n {\n \tif (value) {\n@@ -230,6 +241,13 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (strcmp(var, \"receive.procreceiverefs\") == 0) {\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tproc_receive_ref_append(value);\n+\t\treturn 0;\n+\t}\n+\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -326,6 +344,79 @@ struct command {\n \tchar ref_name[FLEX_ARRAY]; /* more */\n };\n \n+static void proc_receive_ref_append(const char *prefix)\n+{\n+\tstruct proc_receive_ref *ref_pattern;\n+\tchar *p;\n+\tint len;\n+\n+\tref_pattern = xcalloc(1, sizeof(struct proc_receive_ref));\n+\tp = strchr(prefix, ':');\n+\tif (p) {\n+\t\twhile (prefix < p) {\n+\t\t\tif (*prefix == 'a')\n+\t\t\t\tref_pattern->want_add = 1;\n+\t\t\telse if (*prefix == 'd')\n+\t\t\t\tref_pattern->want_delete = 1;\n+\t\t\telse if (*prefix == 'm')\n+\t\t\t\tref_pattern->want_modify = 1;\n+\t\t\telse if (*prefix == '!')\n+\t\t\t\tref_pattern->negative_ref = 1;\n+\t\t\tprefix++;\n+\t\t}\n+\t\tprefix++;\n+\t} else {\n+\t\tref_pattern->want_add = 1;\n+\t\tref_pattern->want_delete = 1;\n+\t\tref_pattern->want_modify = 1;\n+\t}\n+\tlen = strlen(prefix);\n+\twhile (len && prefix[len - 1] == '/')\n+\t\tlen--;\n+\tref_pattern->ref_prefix = xmemdupz(prefix, len);\n+\tif (!proc_receive_ref) {\n+\t\tproc_receive_ref = ref_pattern;\n+\t} else {\n+\t\tstruct proc_receive_ref *end;\n+\n+\t\tend = proc_receive_ref;\n+\t\twhile (end->next)\n+\t\t\tend = end->next;\n+\t\tend->next = ref_pattern;\n+\t}\n+}\n+\n+static int proc_receive_ref_matches(struct command *cmd)\n+{\n+\tstruct proc_receive_ref *p;\n+\n+\tif (!proc_receive_ref)\n+\t\treturn 0;\n+\n+\tfor (p = proc_receive_ref; p; p = p->next) {\n+\t\tconst char *match = p->ref_prefix;\n+\t\tconst char *remains;\n+\n+\t\tif (!p->want_add && is_null_oid(&cmd->old_oid))\n+\t\t\tcontinue;\n+\t\telse if (!p->want_delete && is_null_oid(&cmd->new_oid))\n+\t\t\tcontinue;\n+\t\telse if (!p->want_modify &&\n+\t\t\t !is_null_oid(&cmd->old_oid) &&\n+\t\t\t !is_null_oid(&cmd->new_oid))\n+\t\t\tcontinue;\n+\n+\t\tif (skip_prefix(cmd->ref_name, match, &remains) &&\n+\t\t    (!*remains || *remains == '/')) {\n+\t\t\tif (!p->negative_ref)\n+\t\t\t\treturn 1;\n+\t\t} else if (p->negative_ref) {\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2)));\n static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2)));\n \n@@ -1814,14 +1905,15 @@ static void execute_commands(struct command *commands,\n \t * Try to find commands that have special prefix in their reference names,\n \t * and mark them to run an external \"proc-receive\" hook later.\n \t */\n-\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n-\t\t\tcontinue;\n+\tif (proc_receive_ref) {\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\t\tif (!should_process_cmd(cmd))\n+\t\t\t\tcontinue;\n \n-\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n-\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n-\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n-\t\t\trun_proc_receive = 1;\n+\t\t\tif (proc_receive_ref_matches(cmd)) {\n+\t\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\t\trun_proc_receive = 1;\n+\t\t\t}\n \t\t}\n \t}\n \ndiff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh\nindex bf410dc418..dc2cf4a522 100644\n--- a/t/t5411/once-0010-report-status-v1.sh\n+++ b/t/t5411/once-0010-report-status-v1.sh\n@@ -1,3 +1,7 @@\n+test_expect_success \"setup receive.procReceiveRefs\" '\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs/for\n+'\n+\n test_expect_success \"setup proc-receive hook\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\ndiff --git a/t/t5411/test-0010-proc-receive-settings.sh b/t/t5411/test-0010-proc-receive-settings.sh\nnew file mode 100644\nindex 0000000000..a36809927b\n--- /dev/null\n+++ b/t/t5411/test-0010-proc-receive-settings.sh\n@@ -0,0 +1,7 @@\n+test_expect_success \"add two receive.procReceiveRefs settings\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit config --add receive.procReceiveRefs refs/for &&\n+\t\tgit config --add receive.procReceiveRefs refs/review/\n+\t)\n+'\ndiff --git a/t/t5411/test-0040-process-all-refs.sh b/t/t5411/test-0040-process-all-refs.sh\nnew file mode 100644\nindex 0000000000..b07c999f53\n--- /dev/null\n+++ b/t/t5411/test-0040-process-all-refs.sh\n@@ -0,0 +1,113 @@\n+test_expect_success \"config receive.procReceiveRefs = refs ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config --unset-all receive.procReceiveRefs &&\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup upstream branches ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $B &&\n+\t\tgit update-ref refs/heads/foo $A &&\n+\t\tgit update-ref refs/heads/bar $A &&\n+\t\tgit update-ref refs/heads/baz $A\n+\t)\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/foo\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/bar\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"proc-receive: process all refs ($PROTOCOL)\" '\n+\tgit -C workbench push -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/foo\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/bar\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> bar\n+\t - [deleted] foo\n+\t + <OID-B>...<OID-A> HEAD -> master (forced update)\n+\t <OID-A>..<OID-B> HEAD -> refs/pull/123/head\n+\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             bar(A)  baz(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+'\ndiff --git a/t/t5411/test-0041-process-all-refs--porcelain.sh b/t/t5411/test-0041-process-all-refs--porcelain.sh\nnew file mode 100644\nindex 0000000000..0dd9824616\n--- /dev/null\n+++ b/t/t5411/test-0041-process-all-refs--porcelain.sh\n@@ -0,0 +1,114 @@\n+test_expect_success \"config receive.procReceiveRefs = refs ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" config --unset-all receive.procReceiveRefs &&\n+\tgit -C \"$upstream\" config --add receive.procReceiveRefs refs\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup upstream branches ($PROTOCOL/porcelain)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $B &&\n+\t\tgit update-ref refs/heads/foo $A &&\n+\t\tgit update-ref refs/heads/bar $A &&\n+\t\tgit update-ref refs/heads/baz $A\n+\t)\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook ($PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/foo\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/heads/bar\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -f      : master(A)             (NULL)  (B)              refs/for/master/topic(A)  refs/for/next/topic(A)\n+test_expect_success \"proc-receive: process all refs ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain -f origin \\\n+\t\tHEAD:refs/heads/master \\\n+\t\t:refs/heads/foo \\\n+\t\t$B:refs/heads/bar \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/foo\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/heads/bar\n+\tremote: proc-receive> option fall-through\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head\n+\tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/bar    <OID-A>..<OID-B>\n+\t-    :refs/heads/foo    [deleted]\n+\t+    HEAD:refs/heads/master    <OID-B>...<OID-A> (forced update)\n+\t     HEAD:refs/pull/123/head    <OID-A>..<OID-B>\n+\t+    HEAD:refs/pull/124/head    <OID-B>...<OID-A> (forced update)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             bar(A)  baz(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+'\ndiff --git a/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh b/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh\nnew file mode 100644\nindex 0000000000..c22849cbe2\n--- /dev/null\n+++ b/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh\n@@ -0,0 +1,135 @@\n+test_expect_success \"config receive.procReceiveRefs with modifiers ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit config --unset-all receive.procReceiveRefs &&\n+\t\tgit config --add receive.procReceiveRefs m:refs/heads/master &&\n+\t\tgit config --add receive.procReceiveRefs ad:refs/heads &&\n+\t\tgit config --add receive.procReceiveRefs \"a!:refs/heads\"\n+\t)\n+'\n+\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/tags/v123 \" \\\n+\t\t-r \"option refname refs/pull/124/head\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : master(B)  tags/v123\n+test_expect_success \"proc-receive: update branch and new tag ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tv123 >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <TAG-v123> refs/tags/v123\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/tags/v123\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head\n+\tremote: post-receive< <ZERO-OID> <TAG-v123> refs/pull/124/head\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> refs/pull/123/head\n+\t * [new reference] v123 -> refs/pull/124/head\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"setup upstream: create tags/v123 ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/topic $A &&\n+\tgit -C \"$upstream\" update-ref refs/tags/v123 $TAG &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/topic\n+\t<TAG-v123> refs/tags/v123\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $ZERO_OID\" \\\n+\t\t-r \"ok refs/heads/next\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option new-oid $A\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)  topic(A)  tags/v123\n+# Refs of workbench: master(A)            tags/v123\n+# git push         : NULL       topic(B)  NULL       next(A)\n+test_expect_success \"proc-receive: create/delete branch, and delete tag ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t:refs/heads/master \\\n+\t\t$B:refs/heads/topic \\\n+\t\t$A:refs/heads/next \\\n+\t\t:refs/tags/v123 >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/master\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/topic\n+\tremote: pre-receive< <TAG-v123> <ZERO-OID> refs/tags/v123\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/master\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <ZERO-OID>\n+\tremote: proc-receive> ok refs/heads/next\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <ZERO-OID> refs/pull/123/head\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/topic\n+\tremote: post-receive< <TAG-v123> <ZERO-OID> refs/tags/v123\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/124/head\n+\tTo <URL/of/upstream.git>\n+\t - [deleted] refs/pull/123/head\n+\t <OID-A>..<OID-B> <COMMIT-B> -> topic\n+\t - [deleted] v123\n+\t * [new reference] <COMMIT-A> -> refs/pull/124/head\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-B> refs/heads/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n-- \n2.26.2.543.g44b58e439b\n\n"},{"id":"404616","messageId":"20200827154551.5966-4-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200824174202.11710-1-worldhello.net@gmail.com","subject":"[PATCH v19 03/10] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-27T15:45:44Z","receivedAt":"2020-08-27T15:46:14Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nGit calls an internal `execute_commands` function to handle commands\nsent from client to `git-receive-pack`.  Regardless of what references\nthe user pushes, git creates or updates the corresponding references if\nthe user has write-permission.  A contributor who has no\nwrite-permission, cannot push to the repository directly.  So, the\ncontributor has to write commits to an alternate location, and sends\npull request by emails or by other ways.  We call this workflow as a\ndistributed workflow.\n\nIt would be more convenient to work in a centralized workflow like what\nGerrit provided for some cases.  For example, a read-only user who\ncannot push to a branch directly can run the following `git push`\ncommand to push commits to a pseudo reference (has a prefix \"refs/for/\",\nnot \"refs/heads/\") to create a code review.\n\n    git push origin \\\n        HEAD:refs/for/<branch-name>/<session>\n\nThe `<branch-name>` in the above example can be as simple as \"master\",\nor a more complicated branch name like \"foo/bar\".  The `<session>` in\nthe above example command can be the local branch name of the client\nside, such as \"my/topic\".\n\nWe cannot implement a centralized workflow elegantly by using\n\"pre-receive\" + \"post-receive\", because Git will call the internal\nfunction \"execute_commands\" to create references (even the special\npseudo reference) between these two hooks.  Even though we can delete\nthe temporarily created pseudo reference via the \"post-receive\" hook,\nhaving a temporary reference is not safe for concurrent pushes.\n\nSo, add a filter and a new handler to support this kind of workflow.\nThe filter will check the prefix of the reference name, and if the\ncommand has a special reference name, the filter will turn a specific\nfield (`run_proc_receive`) on for the command.  Commands with this filed\nturned on will be executed by a new handler (a hook named\n\"proc-receive\") instead of the internal `execute_commands` function.\nWe can use this \"proc-receive\" command to create pull requests or send\nemails for code review.\n\nSuggested by Junio, this \"proc-receive\" hook reads the commands,\npush-options (optional), and send result using a protocol in pkt-line\nformat.  In the following example, the letter \"S\" stands for\n\"receive-pack\" and letter \"H\" stands for the hook.\n\n    # Version and features negotiation.\n    S: PKT-LINE(version=1\\0push-options atomic...)\n    S: flush-pkt\n    H: PKT-LINE(version=1\\0push-options...)\n    H: flush-pkt\n\n    # Send commands from server to the hook.\n    S: PKT-LINE(<old-oid> <new-oid> <ref>)\n    S: ... ...\n    S: flush-pkt\n    # Send push-options only if the 'push-options' feature is enabled.\n    S: PKT-LINE(push-option)\n    S: ... ...\n    S: flush-pkt\n\n    # Receive result from the hook.\n    # OK, run this command successfully.\n    H: PKT-LINE(ok <ref>)\n    # NO, I reject it.\n    H: PKT-LINE(ng <ref> <reason>)\n    # Fall through, let 'receive-pack' to execute it.\n    H: PKT-LINE(ok <ref>)\n    H: PKT-LINE(option fall-through)\n    # OK, but has an alternate reference.  The alternate reference name\n    # and other status can be given in options\n    H: PKT-LINE(ok <ref>)\n    H: PKT-LINE(option refname <refname>)\n    H: PKT-LINE(option old-oid <old-oid>)\n    H: PKT-LINE(option new-oid <new-oid>)\n    H: PKT-LINE(option forced-update)\n    H: ... ...\n    H: flush-pkt\n\nAfter receiving a command, the hook will execute the command, and may\ncreate/update different reference.  For example, a command for a pseudo\nreference \"refs/for/master/topic\" may create/update different reference\nsuch as \"refs/pull/123/head\".  The alternate reference name and other\nstatus are given in option lines.\n\nThe list of commands returned from \"proc-receive\" will replace the\nrelevant commands that are sent from user to \"receive-pack\", and\n\"receive-pack\" will continue to run the \"execute_commands\" function and\nother routines.  Finally, the result of the execution of these commands\nwill be reported to end user.\n\nThe reporting function from \"receive-pack\" to \"send-pack\" will be\nextended in latter commit just like what the \"proc-receive\" hook reports\nto \"receive-pack\".\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Makefile                                      |   1 +\n builtin/receive-pack.c                        | 296 +++++++++++++++++-\n remote.h                                      |   8 +\n t/helper/test-proc-receive.c                  | 176 +++++++++++\n t/helper/test-tool.c                          |   1 +\n t/helper/test-tool.h                          |   1 +\n t/t5411/test-0011-no-hook-error.sh            |  64 ++++\n t/t5411/test-0012-no-hook-error--porcelain.sh |  66 ++++\n t/t5411/test-0013-bad-protocol.sh             | 217 +++++++++++++\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 160 ++++++++++\n t/t5411/test-0020-report-ng.sh                |  67 ++++\n t/t5411/test-0021-report-ng--porcelain.sh     |  69 ++++\n t/t5411/test-0022-report-unexpect-ref.sh      |  45 +++\n ...est-0023-report-unexpect-ref--porcelain.sh |  46 +++\n t/t5411/test-0024-report-unknown-ref.sh       |  34 ++\n ...test-0025-report-unknown-ref--porcelain.sh |  35 +++\n t/t5411/test-0026-push-options.sh             |  79 +++++\n t/t5411/test-0027-push-options--porcelain.sh  |  82 +++++\n t/t5411/test-0030-report-ok.sh                |  35 +++\n t/t5411/test-0031-report-ok--porcelain.sh     |  36 +++\n t/t5411/test-0032-report-with-options.sh      | 256 +++++++++++++++\n ...est-0033-report-with-options--porcelain.sh | 265 ++++++++++++++++\n t/t5411/test-0034-report-ft.sh                |  44 +++\n t/t5411/test-0035-report-ft--porcelain.sh     |  45 +++\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 159 ++++++++++\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 162 ++++++++++\n t/t5411/test-0038-report-mixed-refs.sh        |  89 ++++++\n .../test-0039-report-mixed-refs--porcelain.sh |  91 ++++++\n 28 files changed, 2626 insertions(+), 3 deletions(-)\n create mode 100644 t/helper/test-proc-receive.c\n create mode 100644 t/t5411/test-0011-no-hook-error.sh\n create mode 100644 t/t5411/test-0012-no-hook-error--porcelain.sh\n create mode 100644 t/t5411/test-0013-bad-protocol.sh\n create mode 100644 t/t5411/test-0014-bad-protocol--porcelain.sh\n create mode 100644 t/t5411/test-0020-report-ng.sh\n create mode 100644 t/t5411/test-0021-report-ng--porcelain.sh\n create mode 100644 t/t5411/test-0022-report-unexpect-ref.sh\n create mode 100644 t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n create mode 100644 t/t5411/test-0024-report-unknown-ref.sh\n create mode 100644 t/t5411/test-0025-report-unknown-ref--porcelain.sh\n create mode 100644 t/t5411/test-0026-push-options.sh\n create mode 100644 t/t5411/test-0027-push-options--porcelain.sh\n create mode 100644 t/t5411/test-0030-report-ok.sh\n create mode 100644 t/t5411/test-0031-report-ok--porcelain.sh\n create mode 100644 t/t5411/test-0032-report-with-options.sh\n create mode 100644 t/t5411/test-0033-report-with-options--porcelain.sh\n create mode 100644 t/t5411/test-0034-report-ft.sh\n create mode 100644 t/t5411/test-0035-report-ft--porcelain.sh\n create mode 100644 t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n create mode 100644 t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n create mode 100644 t/t5411/test-0038-report-mixed-refs.sh\n create mode 100644 t/t5411/test-0039-report-mixed-refs--porcelain.sh\n\ndiff --git a/Makefile b/Makefile\nindex 65f8cfb236..48c048584e 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -719,6 +719,7 @@ TEST_BUILTINS_OBJS += test-parse-pathspec-file.o\n TEST_BUILTINS_OBJS += test-path-utils.o\n TEST_BUILTINS_OBJS += test-pkt-line.o\n TEST_BUILTINS_OBJS += test-prio-queue.o\n+TEST_BUILTINS_OBJS += test-proc-receive.o\n TEST_BUILTINS_OBJS += test-progress.o\n TEST_BUILTINS_OBJS += test-reach.o\n TEST_BUILTINS_OBJS += test-read-cache.o\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 439f29d6c7..d4614f6f67 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -310,11 +310,15 @@ static void write_head_info(void)\n \tpacket_flush(1);\n }\n \n+#define RUN_PROC_RECEIVE_SCHEDULED\t1\n+#define RUN_PROC_RECEIVE_RETURNED\t2\n struct command {\n \tstruct command *next;\n \tconst char *error_string;\n+\tstruct ref_push_report *report;\n \tunsigned int skip_update:1,\n-\t\t     did_not_exist:1;\n+\t\t     did_not_exist:1,\n+\t\t     run_proc_receive:2;\n \tint index;\n \tstruct object_id old_oid;\n \tstruct object_id new_oid;\n@@ -840,6 +844,268 @@ static int run_update_hook(struct command *cmd)\n \treturn finish_command(&proc);\n }\n \n+static struct command *find_command_by_refname(struct command *list,\n+\t\t\t\t\t       const char *refname)\n+{\n+\tfor (; list; list = list->next)\n+\t\tif (!strcmp(list->ref_name, refname))\n+\t\t\treturn list;\n+\treturn NULL;\n+}\n+\n+static int read_proc_receive_report(struct packet_reader *reader,\n+\t\t\t\t    struct command *commands,\n+\t\t\t\t    struct strbuf *errmsg)\n+{\n+\tstruct command *cmd;\n+\tstruct command *hint = NULL;\n+\tstruct ref_push_report *report = NULL;\n+\tint new_report = 0;\n+\tint code = 0;\n+\tint once = 0;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *head;\n+\t\tconst char *refname;\n+\t\tchar *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\thead = reader->line;\n+\t\tp = strchr(head, ' ');\n+\t\tif (!p) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported incomplete status line: '%s'\\n\", head);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\t*p++ = '\\0';\n+\t\tif (!strcmp(head, \"option\")) {\n+\t\t\tconst char *key, *val;\n+\n+\t\t\tif (!hint || !(report || new_report)) {\n+\t\t\t\tif (!once++)\n+\t\t\t\t\tstrbuf_addstr(errmsg, \"proc-receive reported 'option' without a matching 'ok/ng' directive\\n\");\n+\t\t\t\tcode = -1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tif (new_report) {\n+\t\t\t\tif (!hint->report) {\n+\t\t\t\t\thint->report = xcalloc(1, sizeof(struct ref_push_report));\n+\t\t\t\t\treport = hint->report;\n+\t\t\t\t} else {\n+\t\t\t\t\treport = hint->report;\n+\t\t\t\t\twhile (report->next)\n+\t\t\t\t\t\treport = report->next;\n+\t\t\t\t\treport->next = xcalloc(1, sizeof(struct ref_push_report));\n+\t\t\t\t\treport = report->next;\n+\t\t\t\t}\n+\t\t\t\tnew_report = 0;\n+\t\t\t}\n+\t\t\tkey = p;\n+\t\t\tp = strchr(key, ' ');\n+\t\t\tif (p)\n+\t\t\t\t*p++ = '\\0';\n+\t\t\tval = p;\n+\t\t\tif (!strcmp(key, \"refname\"))\n+\t\t\t\treport->ref_name = xstrdup_or_null(val);\n+\t\t\telse if (!strcmp(key, \"old-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &old_oid, &val))\n+\t\t\t\treport->old_oid = oiddup(&old_oid);\n+\t\t\telse if (!strcmp(key, \"new-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &new_oid, &val))\n+\t\t\t\treport->new_oid = oiddup(&new_oid);\n+\t\t\telse if (!strcmp(key, \"forced-update\"))\n+\t\t\t\treport->forced_update = 1;\n+\t\t\telse if (!strcmp(key, \"fall-through\"))\n+\t\t\t\t/* Fall through, let 'receive-pack' to execute it. */\n+\t\t\t\thint->run_proc_receive = 0;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\treport = NULL;\n+\t\tnew_report = 0;\n+\t\trefname = p;\n+\t\tp = strchr(refname, ' ');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n+\t\tif (strcmp(head, \"ok\") && strcmp(head, \"ng\")) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported bad status '%s' on ref '%s'\\n\",\n+\t\t\t\t    head, refname);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/* first try searching at our hint, falling back to all refs */\n+\t\tif (hint)\n+\t\t\thint = find_command_by_refname(hint, refname);\n+\t\tif (!hint)\n+\t\t\thint = find_command_by_refname(commands, refname);\n+\t\tif (!hint) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unknown ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!hint->run_proc_receive) {\n+\t\t\tstrbuf_addf(errmsg, \"proc-receive reported status on unexpected ref: %s\\n\",\n+\t\t\t\t    refname);\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\thint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;\n+\t\tif (!strcmp(head, \"ng\")) {\n+\t\t\tif (p)\n+\t\t\t\thint->error_string = xstrdup(p);\n+\t\t\telse\n+\t\t\t\thint->error_string = \"failed\";\n+\t\t\tcode = -1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tnew_report = 1;\n+\t}\n+\n+\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\tif (cmd->run_proc_receive && !cmd->error_string &&\n+\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {\n+\t\t    cmd->error_string = \"proc-receive failed to report status\";\n+\t\t    code = -1;\n+\t\t}\n+\treturn code;\n+}\n+\n+static int run_proc_receive_hook(struct command *commands,\n+\t\t\t\t const struct string_list *push_options)\n+{\n+\tstruct child_process proc = CHILD_PROCESS_INIT;\n+\tstruct async muxer;\n+\tstruct command *cmd;\n+\tconst char *argv[2];\n+\tstruct packet_reader reader;\n+\tstruct strbuf cap = STRBUF_INIT;\n+\tstruct strbuf errmsg = STRBUF_INIT;\n+\tint hook_use_push_options = 0;\n+\tint version = 0;\n+\tint code;\n+\n+\targv[0] = find_hook(\"proc-receive\");\n+\tif (!argv[0]) {\n+\t\trp_error(\"cannot find hook 'proc-receive'\");\n+\t\treturn -1;\n+\t}\n+\targv[1] = NULL;\n+\n+\tproc.argv = argv;\n+\tproc.in = -1;\n+\tproc.out = -1;\n+\tproc.trace2_hook_name = \"proc-receive\";\n+\n+\tif (use_sideband) {\n+\t\tmemset(&muxer, 0, sizeof(muxer));\n+\t\tmuxer.proc = copy_to_sideband;\n+\t\tmuxer.in = -1;\n+\t\tcode = start_async(&muxer);\n+\t\tif (code)\n+\t\t\treturn code;\n+\t\tproc.err = muxer.in;\n+\t} else {\n+\t\tproc.err = 0;\n+\t}\n+\n+\tcode = start_command(&proc);\n+\tif (code) {\n+\t\tif (use_sideband)\n+\t\t\tfinish_async(&muxer);\n+\t\treturn code;\n+\t}\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\n+\t/* Version negotiaton */\n+\tpacket_reader_init(&reader, proc.out, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_GENTLE_ON_EOF);\n+\tif (use_atomic)\n+\t\tstrbuf_addstr(&cap, \" atomic\");\n+\tif (use_push_options)\n+\t\tstrbuf_addstr(&cap, \" push-options\");\n+\tif (cap.len) {\n+\t\tpacket_write_fmt(proc.in, \"version=1%c%s\\n\", '\\0', cap.buf + 1);\n+\t\tstrbuf_release(&cap);\n+\t} else {\n+\t\tpacket_write_fmt(proc.in, \"version=1\\n\");\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader.pktlen > 8 && starts_with(reader.line, \"version=\")) {\n+\t\t\tversion = atoi(reader.line + 8);\n+\t\t\tlinelen = strlen(reader.line);\n+\t\t\tif (linelen < reader.pktlen) {\n+\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\thook_use_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (version != 1) {\n+\t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n+\t\t\t    version);\n+\t\tcode = -1;\n+\t\tgoto cleanup;\n+\t}\n+\n+\t/* Send commands */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)\n+\t\t\tcontinue;\n+\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n+\t\t\t\t oid_to_hex(&cmd->old_oid),\n+\t\t\t\t oid_to_hex(&cmd->new_oid),\n+\t\t\t\t cmd->ref_name);\n+\t}\n+\tpacket_flush(proc.in);\n+\n+\t/* Send push options */\n+\tif (hook_use_push_options) {\n+\t\tstruct string_list_item *item;\n+\n+\t\tfor_each_string_list_item(item, push_options)\n+\t\t\tpacket_write_fmt(proc.in, \"%s\", item->string);\n+\t\tpacket_flush(proc.in);\n+\t}\n+\n+\t/* Read result from proc-receive */\n+\tcode = read_proc_receive_report(&reader, commands, &errmsg);\n+\n+cleanup:\n+\tclose(proc.in);\n+\tclose(proc.out);\n+\tif (use_sideband)\n+\t\tfinish_async(&muxer);\n+\tif (finish_command(&proc))\n+\t\tcode = -1;\n+\tif (errmsg.len >0) {\n+\t\tchar *p = errmsg.buf;\n+\n+\t\tp += errmsg.len - 1;\n+\t\tif (*p == '\\n')\n+\t\t\t*p = '\\0';\n+\t\trp_error(\"%s\", errmsg.buf);\n+\t\tstrbuf_release(&errmsg);\n+\t}\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn code;\n+}\n+\n static char *refuse_unconfigured_deny_msg =\n \tN_(\"By default, updating the current branch in a non-bare repository\\n\"\n \t   \"is denied, because it will make the index and work tree inconsistent\\n\"\n@@ -1415,7 +1681,7 @@ static void execute_commands_non_atomic(struct command *commands,\n \tstruct strbuf err = STRBUF_INIT;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\ttransaction = ref_transaction_begin(&err);\n@@ -1455,7 +1721,7 @@ static void execute_commands_atomic(struct command *commands,\n \t}\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!should_process_cmd(cmd))\n+\t\tif (!should_process_cmd(cmd) || cmd->run_proc_receive)\n \t\t\tcontinue;\n \n \t\tcmd->error_string = update(cmd, si);\n@@ -1491,6 +1757,7 @@ static void execute_commands(struct command *commands,\n \tstruct iterate_data data;\n \tstruct async muxer;\n \tint err_fd = 0;\n+\tint run_proc_receive = 0;\n \n \tif (unpacker_error) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n@@ -1520,6 +1787,21 @@ static void execute_commands(struct command *commands,\n \n \treject_updates_to_hidden(commands);\n \n+\t/*\n+\t * Try to find commands that have special prefix in their reference names,\n+\t * and mark them to run an external \"proc-receive\" hook later.\n+\t */\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tif (!should_process_cmd(cmd))\n+\t\t\tcontinue;\n+\n+\t\t/* TODO: replace the fixed prefix by looking up git config variables. */\n+\t\tif (!strncmp(cmd->ref_name, \"refs/for/\", 9)) {\n+\t\t\tcmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;\n+\t\t\trun_proc_receive = 1;\n+\t\t}\n+\t}\n+\n \tif (run_receive_hook(commands, \"pre-receive\", 0, push_options)) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next) {\n \t\t\tif (!cmd->error_string)\n@@ -1546,6 +1828,14 @@ static void execute_commands(struct command *commands,\n \tfree(head_name_to_free);\n \thead_name = head_name_to_free = resolve_refdup(\"HEAD\", 0, NULL, NULL);\n \n+\tif (run_proc_receive &&\n+\t    run_proc_receive_hook(commands, push_options))\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tif (!cmd->error_string &&\n+\t\t\t    !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED) &&\n+\t\t\t    (cmd->run_proc_receive || use_atomic))\n+\t\t\t\tcmd->error_string = \"fail to run proc-receive hook\";\n+\n \tif (use_atomic)\n \t\texecute_commands_atomic(commands, si);\n \telse\ndiff --git a/remote.h b/remote.h\nindex 5e3ea5a26d..519349333a 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -93,6 +93,14 @@ int for_each_remote(each_remote_fn fn, void *priv);\n \n int remote_has_url(struct remote *remote, const char *url);\n \n+struct ref_push_report {\n+\tconst char *ref_name;\n+\tstruct object_id *old_oid;\n+\tstruct object_id *new_oid;\n+\tunsigned int forced_update:1;\n+\tstruct ref_push_report *next;\n+};\n+\n struct ref {\n \tstruct ref *next;\n \tstruct object_id old_oid;\ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nnew file mode 100644\nindex 0000000000..42164d9898\n--- /dev/null\n+++ b/t/helper/test-proc-receive.c\n@@ -0,0 +1,176 @@\n+#include \"cache.h\"\n+#include \"connect.h\"\n+#include \"parse-options.h\"\n+#include \"pkt-line.h\"\n+#include \"sigchain.h\"\n+#include \"test-tool.h\"\n+\n+static const char *proc_receive_usage[] = {\n+\t\"test-tool proc-receive [<options>...]\",\n+\tNULL\n+};\n+\n+static int die_version;\n+static int die_readline;\n+static int no_push_options;\n+static int use_atomic;\n+static int use_push_options;\n+static int verbose;\n+static int version = 1;\n+static struct string_list returns = STRING_LIST_INIT_NODUP;\n+\n+struct command {\n+\tstruct command *next;\n+\tconst char *error_string;\n+\tunsigned int skip_update:1,\n+\t\t     did_not_exist:1;\n+\tint index;\n+\tstruct object_id old_oid;\n+\tstruct object_id new_oid;\n+\tchar ref_name[FLEX_ARRAY]; /* more */\n+};\n+\n+static void proc_receive_verison(struct packet_reader *reader) {\n+\tint server_version = 0;\n+\n+\tfor (;;) {\n+\t\tint linelen;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (reader->pktlen > 8 && starts_with(reader->line, \"version=\")) {\n+\t\t\tserver_version = atoi(reader->line+8);\n+\t\t\tlinelen = strlen(reader->line);\n+\t\t\tif (linelen < reader->pktlen) {\n+\t\t\t\tconst char *feature_list = reader->line + linelen + 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"atomic\"))\n+\t\t\t\t\tuse_atomic= 1;\n+\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\tuse_push_options = 1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (server_version != 1 || die_version)\n+\t\tdie(\"bad protocol version: %d\", server_version);\n+\n+\tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n+\t\t\t version, '\\0',\n+\t\t\t use_push_options && !no_push_options ? \"push-options\": \"\");\n+\tpacket_flush(1);\n+}\n+\n+static void proc_receive_read_commands(struct packet_reader *reader,\n+\t\t\t\t       struct command **commands)\n+{\n+\tstruct command **tail = commands;\n+\n+\tfor (;;) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tstruct command *cmd;\n+\t\tconst char *refname;\n+\t\tconst char *p;\n+\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    parse_oid_hex(p, &new_oid, &p) ||\n+\t\t    *p++ != ' ' ||\n+\t\t    die_readline)\n+\t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n+\t\t\t    reader->line);\n+\t\trefname = p;\n+\t\tFLEX_ALLOC_STR(cmd, ref_name, refname);\n+\t\toidcpy(&cmd->old_oid, &old_oid);\n+\t\toidcpy(&cmd->new_oid, &new_oid);\n+\n+\t\t*tail = cmd;\n+\t\ttail = &cmd->next;\n+\t}\n+}\n+\n+static void proc_receive_read_push_options(struct packet_reader *reader,\n+\t\t\t\t\t   struct string_list *options)\n+{\n+\n+\tif (no_push_options || !use_push_options)\n+\t       return;\n+\n+\twhile (1) {\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\t\tbreak;\n+\n+\t\tstring_list_append(options, reader->line);\n+\t}\n+}\n+\n+int cmd__proc_receive(int argc, const char **argv)\n+{\n+\tint nongit_ok = 0;\n+\tstruct packet_reader reader;\n+\tstruct command *commands = NULL;\n+\tstruct string_list push_options = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tstruct option options[] = {\n+\t\tOPT_BOOL(0, \"no-push-options\", &no_push_options,\n+\t\t\t \"disable push options\"),\n+\t\tOPT_BOOL(0, \"die-version\", &die_version,\n+\t\t\t \"die during version negotiation\"),\n+\t\tOPT_BOOL(0, \"die-readline\", &die_readline,\n+\t\t\t \"die when readline\"),\n+\t\tOPT_STRING_LIST('r', \"return\", &returns, \"old/new/ref/status/msg\",\n+\t\t\t\t\"return of results\"),\n+\t\tOPT__VERBOSE(&verbose, \"be verbose\"),\n+\t\tOPT_INTEGER('V', \"version\", &version,\n+\t\t\t    \"use this protocol version number\"),\n+\t\tOPT_END()\n+\t};\n+\n+\tsetup_git_directory_gently(&nongit_ok);\n+\n+\targc = parse_options(argc, argv, \"test-tools\", options, proc_receive_usage, 0);\n+\tif (argc > 0)\n+\t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n+\tpacket_reader_init(&reader, 0, NULL, 0,\n+\t\t\t   PACKET_READ_CHOMP_NEWLINE |\n+\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\tproc_receive_verison(&reader);\n+\tproc_receive_read_commands(&reader, &commands);\n+\tproc_receive_read_push_options(&reader, &push_options);\n+\n+\tif (verbose) {\n+\t\tstruct command *cmd;\n+\n+\t\tif (use_push_options || use_atomic)\n+\t\t\tfprintf(stderr, \"proc-receive:%s%s\\n\",\n+\t\t\t\tuse_atomic? \" atomic\": \"\",\n+\t\t\t\tuse_push_options ? \" push_options\": \"\");\n+\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tfprintf(stderr, \"proc-receive< %s %s %s\\n\",\n+\t\t\t\toid_to_hex(&cmd->old_oid),\n+\t\t\t\toid_to_hex(&cmd->new_oid),\n+\t\t\t\tcmd->ref_name);\n+\n+\t\tif (push_options.nr > 0)\n+\t\t\tfor_each_string_list_item(item, &push_options)\n+\t\t\t\tfprintf(stderr, \"proc-receive< %s\\n\", item->string);\n+\n+\t\tif (returns.nr)\n+\t\t\tfor_each_string_list_item(item, &returns)\n+\t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n+\t}\n+\n+\tif (returns.nr)\n+\t\tfor_each_string_list_item(item, &returns)\n+\t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\n+\tpacket_flush(1);\n+\tsigchain_pop(SIGPIPE);\n+\n+\treturn 0;\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 590b2efca7..a0d3966b29 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -46,6 +46,7 @@ static struct test_cmd cmds[] = {\n \t{ \"path-utils\", cmd__path_utils },\n \t{ \"pkt-line\", cmd__pkt_line },\n \t{ \"prio-queue\", cmd__prio_queue },\n+\t{ \"proc-receive\", cmd__proc_receive},\n \t{ \"progress\", cmd__progress },\n \t{ \"reach\", cmd__reach },\n \t{ \"read-cache\", cmd__read_cache },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex ddc8e990e9..07034d3f38 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -35,6 +35,7 @@ int cmd__parse_pathspec_file(int argc, const char** argv);\n int cmd__path_utils(int argc, const char **argv);\n int cmd__pkt_line(int argc, const char **argv);\n int cmd__prio_queue(int argc, const char **argv);\n+int cmd__proc_receive(int argc, const char **argv);\n int cmd__progress(int argc, const char **argv);\n int cmd__reach(int argc, const char **argv);\n int cmd__read_cache(int argc, const char **argv);\ndiff --git a/t/t5411/test-0011-no-hook-error.sh b/t/t5411/test-0011-no-hook-error.sh\nnew file mode 100644\nindex 0000000000..bb6ec92a92\n--- /dev/null\n+++ b/t/t5411/test-0011-no-hook-error.sh\n@@ -0,0 +1,64 @@\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push --atomic origin \\\n+\t\t$B:master \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] <COMMIT-B> -> master (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> next (fail to run proc-receive hook)\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0012-no-hook-error--porcelain.sh b/t/t5411/test-0012-no-hook-error--porcelain.sh\nnew file mode 100644\nindex 0000000000..4814f74dc2\n--- /dev/null\n+++ b/t/t5411/test-0012-no-hook-error--porcelain.sh\n@@ -0,0 +1,66 @@\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push --atomic: (B)                   next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain --atomic origin \\\n+\t\t$B:master \\\n+\t\tHEAD:next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: error: cannot find hook \"proc-receive\"\n+\tTo <URL/of/upstream.git>\n+\t!    <COMMIT-B>:refs/heads/master    [remote rejected] (fail to run proc-receive hook)\n+\t!    HEAD:refs/heads/next    [remote rejected] (fail to run proc-receive hook)\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh\nnew file mode 100644\nindex 0000000000..c5fe4cb37b\n--- /dev/null\n+++ b/t/t5411/test-0013-bad-protocol.sh\n@@ -0,0 +1,217 @@\n+test_expect_success \"setup proc-receive hook (unknown version, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\t# Check status report for git-push\n+\tsed -n \\\n+\t\t-e \"/^To / { p; n; p; }\" \\\n+\t\t<actual >actual-report &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual-report &&\n+\n+\t# Check error message from \"receive-pack\", but ignore unstable fatal error\n+\t# message (\"remote: fatal: the remote end hung up unexpectedly\") which\n+\t# is different from the remote HTTP server with different locale settings.\n+\tgrep \"^remote: error:\" <actual >actual-error &&\n+\tcat >expect <<-EOF &&\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tEOF\n+\ttest_cmp expect actual-error &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-version, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-version\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-version, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: fatal: bad protocol version: 1\n+\tremote: error: proc-receive version \"0\" is not supported\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-readline, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-readline\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-readline, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\tgrep \"remote: fatal: protocol error: expected \\\"old new ref\\\", got \\\"<ZERO-OID> <COMMIT-A> refs/for/master/topic\\\"\" actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok\n+\tremote: error: proc-receive reported incomplete status line: \"ok\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"xx refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> xx refs/for/master/topic\n+\tremote: error: proc-receive reported bad status \"xx\" on ref \"refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0014-bad-protocol--porcelain.sh b/t/t5411/test-0014-bad-protocol--porcelain.sh\nnew file mode 100644\nindex 0000000000..53b47b0185\n--- /dev/null\n+++ b/t/t5411/test-0014-bad-protocol--porcelain.sh\n@@ -0,0 +1,160 @@\n+test_expect_success \"setup proc-receive hook (unknown version, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --version 2\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\n+\t# Check status report for git-push\n+\tsed -n \\\n+\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n+\t\t<actual >actual-report &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual-report &&\n+\n+\t# Check error message from \"receive-pack\", but ignore unstable fatal error\n+\t# message (\"remote: fatal: the remote end hung up unexpectedly\") which\n+\t# is different from the remote HTTP server with different locale settings.\n+\tgrep \"^remote: error:\" <actual >actual-error &&\n+\tcat >expect <<-EOF &&\n+\tremote: error: proc-receive version \"2\" is not supported\n+\tEOF\n+\ttest_cmp expect actual-error &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (no report, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       next(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic >out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+\n+'\n+\n+test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic\\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok\n+\tremote: error: proc-receive reported incomplete status line: \"ok\"\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"xx refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t\tHEAD:refs/for/master/topic \\\n+\t\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> xx refs/for/master/topic\n+\tremote: error: proc-receive reported bad status \"xx\" on ref \"refs/for/master/topic\"\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0020-report-ng.sh b/t/t5411/test-0020-report-ng.sh\nnew file mode 100644\nindex 0000000000..f726b7ca9c\n--- /dev/null\n+++ b/t/t5411/test-0020-report-ng.sh\n@@ -0,0 +1,67 @@\n+test_expect_success \"setup proc-receive hook (ng, no message, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (failed)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic error msg\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (error msg)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0021-report-ng--porcelain.sh b/t/t5411/test-0021-report-ng--porcelain.sh\nnew file mode 100644\nindex 0000000000..fbf5569103\n--- /dev/null\n+++ b/t/t5411/test-0021-report-ng--porcelain.sh\n@@ -0,0 +1,69 @@\n+test_expect_success \"setup proc-receive hook (ng, no message, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (failed)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ng refs/for/master/topic error msg\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ng refs/for/master/topic error msg\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (error msg)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0022-report-unexpect-ref.sh b/t/t5411/test-0022-report-unexpect-ref.sh\nnew file mode 100644\nindex 0000000000..92a415b929\n--- /dev/null\n+++ b/t/t5411/test-0022-report-unexpect-ref.sh\n@@ -0,0 +1,45 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: error: proc-receive reported status on unexpected ref: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\ndiff --git a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\nnew file mode 100644\nindex 0000000000..acbf93e40a\n--- /dev/null\n+++ b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n@@ -0,0 +1,46 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/heads/master\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic\n+test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/heads/master\n+\tremote: error: proc-receive reported status on unexpected ref: refs/heads/master\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" update-ref refs/heads/master $A\n+'\ndiff --git a/t/t5411/test-0024-report-unknown-ref.sh b/t/t5411/test-0024-report-unknown-ref.sh\nnew file mode 100644\nindex 0000000000..c3946f329a\n--- /dev/null\n+++ b/t/t5411/test-0024-report-unknown-ref.sh\n@@ -0,0 +1,34 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: report unknown reference ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0025-report-unknown-ref--porcelain.sh b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\nnew file mode 100644\nindex 0000000000..d093b1a579\n--- /dev/null\n+++ b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\n@@ -0,0 +1,35 @@\n+test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/a/b/c/my/topic\n+test_expect_success \"proc-receive: report unknown reference ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/a/b/c/my/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: error: proc-receive reported status on unknown ref: refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/a/b/c/my/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh\nnew file mode 100644\nindex 0000000000..d0c4da8b23\n--- /dev/null\n+++ b/t/t5411/test-0026-push-options.sh\n@@ -0,0 +1,79 @@\n+test_expect_success \"setup proc-receive hook and disable push-options ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions false &&\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"proc-receive: not support push options ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n+\t\tactual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"proc-receive: push with options ($PROTOCOL)\" '\n+\tgit -C workbench push \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\ndiff --git a/t/t5411/test-0027-push-options--porcelain.sh b/t/t5411/test-0027-push-options--porcelain.sh\nnew file mode 100644\nindex 0000000000..c89a1e7c57\n--- /dev/null\n+++ b/t/t5411/test-0027-push-options--porcelain.sh\n@@ -0,0 +1,82 @@\n+test_expect_success \"setup proc-receive hook and disable push-options ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions false &&\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       refs/for/master/topic\n+test_expect_success \"proc-receive: not support push options ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push \\\n+\t\t--porcelain \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n+\t\tactual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"enable push options ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/master/topic\n+test_expect_success \"proc-receive: push with options ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push \\\n+\t\t--porcelain \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive: atomic push_options\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< issue=123\n+\tremote: proc-receive< reviewer=user1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             next(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\ndiff --git a/t/t5411/test-0030-report-ok.sh b/t/t5411/test-0030-report-ok.sh\nnew file mode 100644\nindex 0000000000..44c99d3831\n--- /dev/null\n+++ b/t/t5411/test-0030-report-ok.sh\n@@ -0,0 +1,35 @@\n+test_expect_success \"setup proc-receive hook (ok, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0031-report-ok--porcelain.sh b/t/t5411/test-0031-report-ok--porcelain.sh\nnew file mode 100644\nindex 0000000000..3223b26184\n--- /dev/null\n+++ b/t/t5411/test-0031-report-ok--porcelain.sh\n@@ -0,0 +1,36 @@\n+test_expect_success \"setup proc-receive hook (ok, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic\n+test_expect_success \"proc-receive: ok ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nnew file mode 100644\nindex 0000000000..a743aa8018\n--- /dev/null\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -0,0 +1,256 @@\n+test_expect_success \"setup proc-receive hook (option without matching ok, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option without matching ok ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: error: proc-receive reported \"option\" without a matching \"ok/ng\" directive\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and forced-update, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and forced-update ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and old-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid and new-oid, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (report with multiple rewrites, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/a/b/c/topic\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option forced-update\" \\\n+\t\t-r \"option new-oid $A\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/for/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/a/b/c/topic\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option forced-update\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/for/a/b/c/topic\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nnew file mode 100644\nindex 0000000000..439b97b06e\n--- /dev/null\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -0,0 +1,265 @@\n+test_expect_success \"setup proc-receive hook (option without matching ok, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option without matching ok ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: error: proc-receive reported \"option\" without a matching \"ok/ng\" directive\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and forced-update, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and forced-update ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option refname and old-oid, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (option old-oid and new-oid, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (report with multiple rewrites, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/a/b/c/topic\" \\\n+\t\t-r \"ok refs/for/next/topic\" \\\n+\t\t-r \"option refname refs/pull/123/head\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/pull/124/head\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option forced-update\" \\\n+\t\t-r \"option new-oid $A\"\n+\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/next/topic(A)  refs/for/a/b/c/topic(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/next/topic \\\n+\t\tHEAD:refs/for/a/b/c/topic \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/a/b/c/topic\n+\tremote: proc-receive> ok refs/for/next/topic\n+\tremote: proc-receive> option refname refs/pull/123/head\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/pull/124/head\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option forced-update\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/next/topic    [new reference]\n+\t*    HEAD:refs/for/a/b/c/topic    [new reference]\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0034-report-ft.sh b/t/t5411/test-0034-report-ft.sh\nnew file mode 100644\nindex 0000000000..aca2b0676c\n--- /dev/null\n+++ b/t/t5411/test-0034-report-ft.sh\n@@ -0,0 +1,44 @@\n+test_expect_success \"setup proc-receive hook (ft, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option fall-through\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(B)\n+test_expect_success \"proc-receive: fall throught, let receive-pack to execute ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\t$B:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option fall-through\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] <COMMIT-B> -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/for/master/topic\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             refs/for/master/topic(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/for/master/topic\n+'\ndiff --git a/t/t5411/test-0035-report-ft--porcelain.sh b/t/t5411/test-0035-report-ft--porcelain.sh\nnew file mode 100644\nindex 0000000000..30ffffb352\n--- /dev/null\n+++ b/t/t5411/test-0035-report-ft--porcelain.sh\n@@ -0,0 +1,45 @@\n+test_expect_success \"setup proc-receive hook (fall-through, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option fall-through\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(B)\n+test_expect_success \"proc-receive: fall throught, let receive-pack to execute ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\t$B:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option fall-through\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    <COMMIT-B>:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-B> refs/for/master/topic\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(A)             refs/for/master/topic(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/for/master/topic\n+'\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nnew file mode 100644\nindex 0000000000..12acf9ea1f\n--- /dev/null\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -0,0 +1,159 @@\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/23/123/1\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/2\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n+\tgit -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/23/123/1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/2\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\nnew file mode 100644\nindex 0000000000..f4a2c56e14\n--- /dev/null\n+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n@@ -0,0 +1,162 @@\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/1\" \\\n+\t\t-r \"option old-oid $ZERO_OID\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/25/125/1\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/1\n+\tremote: proc-receive> option old-oid <ZERO-OID>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/25/125/1\n+\tremote: proc-receive> option old-oid <COMMIT-B>\n+\tremote: proc-receive> option new-oid <COMMIT-A>\n+\tremote: proc-receive> option forced-update\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/23/123/1\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/changes/24/124/2\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/23/123/1\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/changes/24/124/2\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nnew file mode 100644\nindex 0000000000..1dd6a97a97\n--- /dev/null\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -0,0 +1,89 @@\n+test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/next/topic2\" \\\n+\t\t-r \"ng refs/for/next/topic1 fail to call Web API\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic2 \\\n+\t\tHEAD:refs/for/next/topic1 \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic3 \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n+\tremote: proc-receive> ok refs/for/next/topic2\n+\tremote: proc-receive> ng refs/for/next/topic1 fail to call Web API\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t <OID-A>..<OID-B> <COMMIT-B> -> master\n+\t * [new branch] HEAD -> bar\n+\t * [new branch] HEAD -> baz\n+\t * [new reference] HEAD -> refs/for/next/topic2\n+\t * [new branch] HEAD -> foo\n+\t * [new reference] HEAD -> refs/for/master/topic\n+\t ! [remote rejected] HEAD -> refs/for/next/topic1 (fail to call Web API)\n+\t ! [remote rejected] HEAD -> refs/for/next/topic3 (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit update-ref -d refs/heads/foo &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+'\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nnew file mode 100644\nindex 0000000000..32ebf63dcf\n--- /dev/null\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -0,0 +1,91 @@\n+test_expect_success \"setup proc-receive hook ($PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/next/topic2\" \\\n+\t\t-r \"ng refs/for/next/topic1 fail to call Web API\" \\\n+\t\t-r \"ok refs/for/master/topic\" \\\n+\t\t-r \"option refname refs/for/master/topic\" \\\n+\t\t-r \"option old-oid $A\" \\\n+\t\t-r \"option new-oid $B\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   bar(A)  baz(A)  refs/for/next/topic(A)  foo(A)  refs/for/master/topic(A)\n+test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t$B:refs/heads/master \\\n+\t\tHEAD:refs/heads/bar \\\n+\t\tHEAD:refs/heads/baz \\\n+\t\tHEAD:refs/for/next/topic2 \\\n+\t\tHEAD:refs/for/next/topic1 \\\n+\t\tHEAD:refs/heads/foo \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\tHEAD:refs/for/next/topic3 \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3\n+\tremote: proc-receive> ok refs/for/next/topic2\n+\tremote: proc-receive> ng refs/for/next/topic1 fail to call Web API\n+\tremote: proc-receive> ok refs/for/master/topic\n+\tremote: proc-receive> option refname refs/for/master/topic\n+\tremote: proc-receive> option old-oid <COMMIT-A>\n+\tremote: proc-receive> option new-oid <COMMIT-B>\n+\tremote: # post-receive hook\n+\tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n+\tTo <URL/of/upstream.git>\n+\t     <COMMIT-B>:refs/heads/master    <OID-A>..<OID-B>\n+\t*    HEAD:refs/heads/bar    [new branch]\n+\t*    HEAD:refs/heads/baz    [new branch]\n+\t*    HEAD:refs/for/next/topic2    [new reference]\n+\t*    HEAD:refs/heads/foo    [new branch]\n+\t*    HEAD:refs/for/master/topic    [new reference]\n+\t!    HEAD:refs/for/next/topic1    [remote rejected] (fail to call Web API)\n+\t!    HEAD:refs/for/next/topic3    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/bar\n+\t<COMMIT-A> refs/heads/baz\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Refs of upstream : master(B)             foo(A)  bar(A))  baz(A)\n+# Refs of workbench: master(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\t(\n+\t\tcd \"$upstream\" &&\n+\t\tgit update-ref refs/heads/master $A &&\n+\t\tgit update-ref -d refs/heads/foo &&\n+\t\tgit update-ref -d refs/heads/bar &&\n+\t\tgit update-ref -d refs/heads/baz\n+\t)\n+\n+'\n-- \n2.26.2.543.g44b58e439b\n\n"},{"id":"404615","messageId":"20200827154551.5966-6-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200824174202.11710-1-worldhello.net@gmail.com","subject":"[PATCH v19 05/10] New capability \"report-status-v2\" for git-push","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-27T15:45:46Z","receivedAt":"2020-08-27T15:46:16Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nThe new introduced \"proc-receive\" hook may handle a command for a\npseudo-reference with a zero-old as its old-oid, while the hook may\ncreate or update a reference with different name, different new-oid,\nand different old-oid (the reference may exist already with a non-zero\nold-oid).  Current \"report-status\" protocol cannot report the status for\nsuch reference rewrite.\n\nAdd new capability \"report-status-v2\" and new report protocol which is\nnot backward compatible for report of git-push.\n\nIf a user pushes to a pseudo-reference \"refs/for/master/topic\", and\n\"receive-pack\" creates two new references \"refs/changes/23/123/1\" and\n\"refs/changes/24/124/1\", for client without the knowledge of\n\"report-status-v2\", \"receive-pack\" will only send \"ok/ng\" directives in\nthe report, such as:\n\n    ok ref/for/master/topic\n\nBut for client which has the knowledge of \"report-status-v2\",\n\"receive-pack\" will use \"option\" directives to report more attributes\nfor the reference given by the above \"ok/ng\" directive.\n\n    ok refs/for/master/topic\n    option refname refs/changes/23/123/1\n    option new-oid <new-oid>\n    ok refs/for/master/topic\n    option refname refs/changes/24/124/1\n    option new-oid <new-oid>\n\nThe client will report two new created references to the end user.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSuggested-by: Jeff King <peff@peff.net>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c                        |  54 +++++++-\n builtin/send-pack.c                           |  19 +++\n remote.h                                      |   1 +\n send-pack.c                                   | 101 +++++++++++---\n t/t5411-proc-receive-hook.sh                  |   9 ++\n t/t5411/once-0010-report-status-v1.sh         |  90 ++++++++++++\n t/t5411/test-0032-report-with-options.sh      |  14 +-\n ...est-0033-report-with-options--porcelain.sh |  14 +-\n ...t-0036-report-multi-rewrite-for-one-ref.sh |  11 +-\n ...rt-multi-rewrite-for-one-ref--porcelain.sh |  11 +-\n t/t5411/test-0038-report-mixed-refs.sh        |   2 +-\n .../test-0039-report-mixed-refs--porcelain.sh |   2 +-\n transport-helper.c                            | 128 ++++++++++++++----\n transport.c                                   | 116 ++++++++++++----\n 14 files changed, 472 insertions(+), 100 deletions(-)\n create mode 100644 t/t5411/once-0010-report-status-v1.sh\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 0390f2dc97..e3dfb17ad4 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -57,6 +57,7 @@ static int advertise_push_options;\n static int unpack_limit = 100;\n static off_t max_input_size;\n static int report_status;\n+static int report_status_v2;\n static int use_sideband;\n static int use_atomic;\n static int use_push_options;\n@@ -240,7 +241,7 @@ static void show_ref(const char *path, const struct object_id *oid)\n \t\tstruct strbuf cap = STRBUF_INIT;\n \n \t\tstrbuf_addstr(&cap,\n-\t\t\t      \"report-status delete-refs side-band-64k quiet\");\n+\t\t\t      \"report-status report-status-v2 delete-refs side-band-64k quiet\");\n \t\tif (advertise_atomic_push)\n \t\t\tstrbuf_addstr(&cap, \" atomic\");\n \t\tif (prefer_ofs_delta)\n@@ -1941,6 +1942,8 @@ static struct command *read_head_info(struct packet_reader *reader,\n \t\t\tint len = 0;\n \t\t\tif (parse_feature_request(feature_list, \"report-status\"))\n \t\t\t\treport_status = 1;\n+\t\t\tif (parse_feature_request(feature_list, \"report-status-v2\"))\n+\t\t\t\treport_status_v2 = 1;\n \t\t\tif (parse_feature_request(feature_list, \"side-band-64k\"))\n \t\t\t\tuse_sideband = LARGE_PACKET_MAX;\n \t\t\tif (parse_feature_request(feature_list, \"quiet\"))\n@@ -2259,6 +2262,51 @@ static void report(struct command *commands, const char *unpack_status)\n \tstrbuf_release(&buf);\n }\n \n+static void report_v2(struct command *commands, const char *unpack_status)\n+{\n+\tstruct command *cmd;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ref_push_report *report;\n+\n+\tpacket_buf_write(&buf, \"unpack %s\\n\",\n+\t\t\t unpack_status ? unpack_status : \"ok\");\n+\tfor (cmd = commands; cmd; cmd = cmd->next) {\n+\t\tint count = 0;\n+\n+\t\tif (cmd->error_string) {\n+\t\t\tpacket_buf_write(&buf, \"ng %s %s\\n\",\n+\t\t\t\t\t cmd->ref_name,\n+\t\t\t\t\t cmd->error_string);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n+\t\t\t\t cmd->ref_name);\n+\t\tfor (report = cmd->report; report; report = report->next) {\n+\t\t\tif (count++ > 0)\n+\t\t\t\tpacket_buf_write(&buf, \"ok %s\\n\",\n+\t\t\t\t\t\t cmd->ref_name);\n+\t\t\tif (report->ref_name)\n+\t\t\t\tpacket_buf_write(&buf, \"option refname %s\\n\",\n+\t\t\t\t\t\t report->ref_name);\n+\t\t\tif (report->old_oid)\n+\t\t\t\tpacket_buf_write(&buf, \"option old-oid %s\\n\",\n+\t\t\t\t\t\t oid_to_hex(report->old_oid));\n+\t\t\tif (report->new_oid)\n+\t\t\t\tpacket_buf_write(&buf, \"option new-oid %s\\n\",\n+\t\t\t\t\t\t oid_to_hex(report->new_oid));\n+\t\t\tif (report->forced_update)\n+\t\t\t\tpacket_buf_write(&buf, \"option forced-update\\n\");\n+\t\t}\n+\t}\n+\tpacket_buf_flush(&buf);\n+\n+\tif (use_sideband)\n+\t\tsend_sideband(1, 1, buf.buf, buf.len, use_sideband);\n+\telse\n+\t\twrite_or_die(1, buf.buf, buf.len);\n+\tstrbuf_release(&buf);\n+}\n+\n static int delete_only(struct command *commands)\n {\n \tstruct command *cmd;\n@@ -2367,7 +2415,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \t\t\t\t &push_options);\n \t\tif (pack_lockfile)\n \t\t\tunlink_or_warn(pack_lockfile);\n-\t\tif (report_status)\n+\t\tif (report_status_v2)\n+\t\t\treport_v2(commands, unpack_status);\n+\t\telse if (report_status)\n \t\t\treport(commands, unpack_status);\n \t\trun_receive_hook(commands, \"post-receive\", 1,\n \t\t\t\t &push_options);\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 2b9610f121..7af148d733 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -29,10 +29,12 @@ static struct send_pack_args args;\n static void print_helper_status(struct ref *ref)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n+\tstruct ref_push_report *report;\n \n \tfor (; ref; ref = ref->next) {\n \t\tconst char *msg = NULL;\n \t\tconst char *res;\n+\t\tint count = 0;\n \n \t\tswitch(ref->status) {\n \t\tcase REF_STATUS_NONE:\n@@ -94,6 +96,23 @@ static void print_helper_status(struct ref *ref)\n \t\t}\n \t\tstrbuf_addch(&buf, '\\n');\n \n+\t\tif (ref->status == REF_STATUS_OK) {\n+\t\t\tfor (report = ref->report; report; report = report->next) {\n+\t\t\t\tif (count++ > 0)\n+\t\t\t\t\tstrbuf_addf(&buf, \"ok %s\\n\", ref->name);\n+\t\t\t\tif (report->ref_name)\n+\t\t\t\t\tstrbuf_addf(&buf, \"option refname %s\\n\",\n+\t\t\t\t\t\treport->ref_name);\n+\t\t\t\tif (report->old_oid)\n+\t\t\t\t\tstrbuf_addf(&buf, \"option old-oid %s\\n\",\n+\t\t\t\t\t\toid_to_hex(report->old_oid));\n+\t\t\t\tif (report->new_oid)\n+\t\t\t\t\tstrbuf_addf(&buf, \"option new-oid %s\\n\",\n+\t\t\t\t\t\toid_to_hex(report->new_oid));\n+\t\t\t\tif (report->forced_update)\n+\t\t\t\t\tstrbuf_addstr(&buf, \"option forced-update\\n\");\n+\t\t\t}\n+\t\t}\n \t\twrite_or_die(1, buf.buf, buf.len);\n \t}\n \tstrbuf_release(&buf);\ndiff --git a/remote.h b/remote.h\nindex 519349333a..eb62a47044 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -148,6 +148,7 @@ struct ref {\n \t\tREF_STATUS_ATOMIC_PUSH_FAILED\n \t} status;\n \tchar *remote_status;\n+\tstruct ref_push_report *report;\n \tstruct ref *peer_ref; /* when renaming */\n \tchar name[FLEX_ARRAY]; /* more */\n };\ndiff --git a/send-pack.c b/send-pack.c\nindex 632f1580ca..2d2f9997ac 100644\n--- a/send-pack.c\n+++ b/send-pack.c\n@@ -154,25 +154,79 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n {\n \tstruct ref *hint;\n \tint ret;\n+\tstruct ref_push_report *report = NULL;\n+\tint new_report = 0;\n+\tint once = 0;\n \n \thint = NULL;\n \tret = receive_unpack_status(reader);\n \twhile (1) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *head;\n \t\tconst char *refname;\n-\t\tchar *msg;\n+\t\tchar *p;\n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n \t\t\tbreak;\n-\t\tif (!starts_with(reader->line, \"ok \") && !starts_with(reader->line, \"ng \")) {\n-\t\t\terror(\"invalid ref status from remote: %s\", reader->line);\n+\t\thead = reader->line;\n+\t\tp = strchr(head, ' ');\n+\t\tif (!p) {\n+\t\t\terror(\"invalid status line from remote: %s\", reader->line);\n \t\t\tret = -1;\n \t\t\tbreak;\n \t\t}\n+\t\t*p++ = '\\0';\n \n-\t\trefname = reader->line + 3;\n-\t\tmsg = strchr(refname, ' ');\n-\t\tif (msg)\n-\t\t\t*msg++ = '\\0';\n+\t\tif (!strcmp(head, \"option\")) {\n+\t\t\tconst char *key, *val;\n \n+\t\t\tif (!hint || !(report || new_report)) {\n+\t\t\t\tif (!once++)\n+\t\t\t\t\terror(\"'option' without a matching 'ok/ng' directive\");\n+\t\t\t\tret = -1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tif (new_report) {\n+\t\t\t\tif (!hint->report) {\n+\t\t\t\t\thint->report = xcalloc(1, sizeof(struct ref_push_report));\n+\t\t\t\t\treport = hint->report;\n+\t\t\t\t} else {\n+\t\t\t\t\treport = hint->report;\n+\t\t\t\t\twhile (report->next)\n+\t\t\t\t\t\treport = report->next;\n+\t\t\t\t\treport->next = xcalloc(1, sizeof(struct ref_push_report));\n+\t\t\t\t\treport = report->next;\n+\t\t\t\t}\n+\t\t\t\tnew_report = 0;\n+\t\t\t}\n+\t\t\tkey = p;\n+\t\t\tp = strchr(key, ' ');\n+\t\t\tif (p)\n+\t\t\t\t*p++ = '\\0';\n+\t\t\tval = p;\n+\t\t\tif (!strcmp(key, \"refname\"))\n+\t\t\t\treport->ref_name = xstrdup_or_null(val);\n+\t\t\telse if (!strcmp(key, \"old-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &old_oid, &val))\n+\t\t\t\treport->old_oid = oiddup(&old_oid);\n+\t\t\telse if (!strcmp(key, \"new-oid\") && val &&\n+\t\t\t\t !parse_oid_hex(val, &new_oid, &val))\n+\t\t\t\treport->new_oid = oiddup(&new_oid);\n+\t\t\telse if (!strcmp(key, \"forced-update\"))\n+\t\t\t\treport->forced_update = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\treport = NULL;\n+\t\tnew_report = 0;\n+\t\tif (strcmp(head, \"ok\") && strcmp(head, \"ng\")) {\n+\t\t\terror(\"invalid ref status from remote: %s\", head);\n+\t\t\tret = -1;\n+\t\t\tbreak;\n+\t\t}\n+\t\trefname = p;\n+\t\tp = strchr(refname, ' ');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n \t\t/* first try searching at our hint, falling back to all refs */\n \t\tif (hint)\n \t\t\thint = find_ref_by_name(hint, refname);\n@@ -180,22 +234,27 @@ static int receive_status(struct packet_reader *reader, struct ref *refs)\n \t\t\thint = find_ref_by_name(refs, refname);\n \t\tif (!hint) {\n \t\t\twarning(\"remote reported status on unknown ref: %s\",\n-\t\t\t\t\trefname);\n+\t\t\t\trefname);\n \t\t\tcontinue;\n \t\t}\n-\t\tif (hint->status != REF_STATUS_EXPECTING_REPORT) {\n+\t\tif (hint->status != REF_STATUS_EXPECTING_REPORT &&\n+\t\t    hint->status != REF_STATUS_OK &&\n+\t\t    hint->status != REF_STATUS_REMOTE_REJECT) {\n \t\t\twarning(\"remote reported status on unexpected ref: %s\",\n-\t\t\t\t\trefname);\n+\t\t\t\trefname);\n \t\t\tcontinue;\n \t\t}\n-\n-\t\tif (reader->line[0] == 'o' && reader->line[1] == 'k')\n-\t\t\thint->status = REF_STATUS_OK;\n-\t\telse\n+\t\tif (!strcmp(head, \"ng\")) {\n \t\t\thint->status = REF_STATUS_REMOTE_REJECT;\n-\t\thint->remote_status = xstrdup_or_null(msg);\n-\t\t/* start our next search from the next ref */\n-\t\thint = hint->next;\n+\t\t\tif (p)\n+\t\t\t\thint->remote_status = xstrdup(p);\n+\t\t\telse\n+\t\t\t\thint->remote_status = \"failed\";\n+\t\t} else {\n+\t\t\thint->status = REF_STATUS_OK;\n+\t\t\thint->remote_status = xstrdup_or_null(p);\n+\t\t\tnew_report = 1;\n+\t\t}\n \t}\n \treturn ret;\n }\n@@ -371,7 +430,9 @@ int send_pack(struct send_pack_args *args,\n \tstruct packet_reader reader;\n \n \t/* Does the other end support the reporting? */\n-\tif (server_supports(\"report-status\"))\n+\tif (server_supports(\"report-status-v2\"))\n+\t\tstatus_report = 2;\n+\telse if (server_supports(\"report-status\"))\n \t\tstatus_report = 1;\n \tif (server_supports(\"delete-refs\"))\n \t\tallow_deleting_refs = 1;\n@@ -423,8 +484,10 @@ int send_pack(struct send_pack_args *args,\n \n \tuse_push_options = push_options_supported && args->push_options;\n \n-\tif (status_report)\n+\tif (status_report == 1)\n \t\tstrbuf_addstr(&cap_buf, \" report-status\");\n+\telse if (status_report == 2)\n+\t\tstrbuf_addstr(&cap_buf, \" report-status-v2\");\n \tif (use_sideband)\n \t\tstrbuf_addstr(&cap_buf, \" side-band-64k\");\n \tif (quiet_supported && (args->quiet || !args->progress))\ndiff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh\nindex 3a684353a8..746487286f 100755\n--- a/t/t5411-proc-receive-hook.sh\n+++ b/t/t5411-proc-receive-hook.sh\n@@ -78,6 +78,15 @@ run_proc_receive_hook_test() {\n # Initialize the upstream repository and local workbench.\n setup_upstream_and_workbench\n \n+# Load test cases that only need to be executed once.\n+for t in  \"$TEST_DIRECTORY\"/t5411/once-*.sh\n+do\n+\t. \"$t\"\n+done\n+\n+# Initialize the upstream repository and local workbench.\n+setup_upstream_and_workbench\n+\n # Run test cases for 'proc-receive' hook on local file protocol.\n run_proc_receive_hook_test local\n \ndiff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh\nnew file mode 100644\nindex 0000000000..bf410dc418\n--- /dev/null\n+++ b/t/t5411/once-0010-report-status-v1.sh\n@@ -0,0 +1,90 @@\n+test_expect_success \"setup proc-receive hook\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/master/topic1\" \\\n+\t\t-r \"option fall-through\" \\\n+\t\t-r \"ok refs/for/master/topic2\" \\\n+\t\t-r \"option refname refs/for/changes/23/123/1\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"ok refs/for/master/topic2\" \\\n+\t\t-r \"option refname refs/for/changes/24/124/2\" \\\n+\t\t-r \"option old-oid $B\" \\\n+\t\t-r \"option new-oid $A\" \\\n+\t\t-r \"option forced-update\" \\\n+\t\t-r \"ng refs/for/next/topic target branch not exist\"\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         : (B)                   refs/for/master/topic1(A)  foo(A)  refs/for/next/topic(A)  refs/for/master/topic2(A)\n+test_expect_success \"proc-receive: report status v1\" '\n+\t{\n+\t\tif test -z \"$GIT_DEFAULT_HASH\" || test \"$GIT_DEFAULT_HASH\" = \"sha1\"\n+\t\tthen\n+\t\t\tprintf \"%s %s refs/heads/master\\0report-status\\n\" \\\n+\t\t\t\t$A $B | packetize\n+\t\telse\n+\t\t\tprintf \"%s %s refs/heads/master\\0report-status object-format=$GIT_DEFAULT_HASH\\n\" \\\n+\t\t\t\t$A $B | packetize\n+\t\tfi &&\n+\t\tprintf \"%s %s refs/for/master/topic1\\n\" \\\n+\t\t\t$ZERO_OID $A | packetize &&\n+\t\tprintf \"%s %s refs/heads/foo\\n\" \\\n+\t\t\t$ZERO_OID $A | packetize &&\n+\t\tprintf \"%s %s refs/for/next/topic\\n\" \\\n+\t\t\t$ZERO_OID $A | packetize &&\n+\t\tprintf \"%s %s refs/for/master/topic2\\n\" \\\n+\t\t\t$ZERO_OID $A | packetize &&\n+\t\tprintf 0000 &&\n+\t\tprintf \"\" | git -C \"$upstream\" pack-objects --stdout\n+\t} | git receive-pack \"$upstream\" --stateless-rpc \\\n+\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t# pre-receive hook\n+\tpre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tpre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n+\tpre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tpre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tpre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2\n+\t# proc-receive hook\n+\tproc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n+\tproc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic\n+\tproc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2\n+\tproc-receive> ok refs/for/master/topic1\n+\tproc-receive> option fall-through\n+\tproc-receive> ok refs/for/master/topic2\n+\tproc-receive> option refname refs/for/changes/23/123/1\n+\tproc-receive> option new-oid <COMMIT-A>\n+\tproc-receive> ok refs/for/master/topic2\n+\tproc-receive> option refname refs/for/changes/24/124/2\n+\tproc-receive> option old-oid <COMMIT-B>\n+\tproc-receive> option new-oid <COMMIT-A>\n+\tproc-receive> option forced-update\n+\tproc-receive> ng refs/for/next/topic target branch not exist\n+\t000eunpack ok\n+\t0019ok refs/heads/master\n+\t001eok refs/for/master/topic1\n+\t0016ok refs/heads/foo\n+\t0033ng refs/for/next/topic target branch not exist\n+\t001eok refs/for/master/topic2\n+\t0000# post-receive hook\n+\tpost-receive< <COMMIT-A> <COMMIT-B> refs/heads/master\n+\tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1\n+\tpost-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo\n+\tpost-receive< <ZERO-OID> <COMMIT-A> refs/for/changes/23/123/1\n+\tpost-receive< <COMMIT-B> <COMMIT-A> refs/for/changes/24/124/2\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/for/master/topic1\n+\t<COMMIT-A> refs/heads/foo\n+\t<COMMIT-B> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nindex c559c207fa..b77b78c49f 100644\n--- a/t/t5411/test-0032-report-with-options.sh\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -56,7 +56,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL)\" '\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -89,7 +89,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -123,7 +123,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL)\n \tremote: # post-receive hook\n \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-B>..<OID-A> HEAD -> refs/pull/123/head\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -155,7 +155,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL)\" '\n \tremote: # post-receive hook\n \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-B>..<OID-A> HEAD -> refs/for/master/topic\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -189,7 +189,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL)\n \tremote: # post-receive hook\n \tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \tEOF\n \ttest_cmp expect actual\n '\n@@ -241,9 +241,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/next/topic\n+\t * [new reference] HEAD -> refs/pull/123/head\n \t * [new reference] HEAD -> refs/for/a/b/c/topic\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n \ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nindex ea9312234e..1fe352b686 100644\n--- a/t/t5411/test-0033-report-with-options--porcelain.sh\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -57,7 +57,7 @@ test_expect_success \"proc-receive: report option refname ($PROTOCOL/porcelain)\"\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -92,7 +92,7 @@ test_expect_success \"proc-receive: report option refname and forced-update ($PRO\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/pull/123/head    [new reference]\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -127,7 +127,7 @@ test_expect_success \"proc-receive: report option refname and old-oid ($PROTOCOL/\n \tremote: # post-receive hook\n \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/pull/123/head    <OID-B>..<OID-A>\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -160,7 +160,7 @@ test_expect_success \"proc-receive: report option old-oid ($PROTOCOL/porcelain)\"\n \tremote: # post-receive hook\n \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-B>..<OID-A>\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -195,7 +195,7 @@ test_expect_success \"proc-receive: report option old-oid and new-oid ($PROTOCOL/\n \tremote: # post-receive hook\n \tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual\n@@ -249,9 +249,9 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porc\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic\n \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/next/topic    [new reference]\n+\t*    HEAD:refs/pull/123/head    [new reference]\n \t*    HEAD:refs/for/a/b/c/topic    [new reference]\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t+    HEAD:refs/pull/124/head    <OID-B>...<OID-A> (forced update)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex 67cc4a8a4c..27d58edd14 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -45,7 +45,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n \tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/24/124/1\n+\t <OID-A>..<OID-B> HEAD -> refs/changes/25/125/1\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -105,7 +107,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/24/124/1\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n+\t + <OID-B>...<OID-A> HEAD -> refs/changes/25/125/1 (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n@@ -152,7 +156,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1\n \tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2\n \tTo <URL/of/upstream.git>\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t * [new reference] HEAD -> refs/changes/23/123/1\n+\t <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\ndiff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\nindex 1dc8551bc6..77b5b22ed4 100644\n--- a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n@@ -45,7 +45,9 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1\n \tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n+\t*    HEAD:refs/changes/24/124/1    [new reference]\n+\t     HEAD:refs/changes/25/125/1    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n@@ -106,7 +108,9 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic\n \tremote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/changes/24/124/1    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n+\t+    HEAD:refs/changes/25/125/1    <OID-B>...<OID-A> (forced update)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n@@ -154,7 +158,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porc\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1\n \tremote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2\n \tTo <URL/of/upstream.git>\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t*    HEAD:refs/changes/23/123/1    [new reference]\n+\t     HEAD:refs/changes/24/124/2    <OID-A>..<OID-B>\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nindex bfc8d586d6..a74a2cb449 100644\n--- a/t/t5411/test-0038-report-mixed-refs.sh\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -60,7 +60,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n \t * [new branch] HEAD -> baz\n \t * [new reference] HEAD -> refs/for/next/topic2\n \t * [new branch] HEAD -> foo\n-\t * [new reference] HEAD -> refs/for/master/topic\n+\t <OID-A>..<OID-B> HEAD -> refs/for/master/topic\n \t ! [remote rejected] HEAD -> refs/for/next/topic1 (fail to call Web API)\n \t ! [remote rejected] HEAD -> refs/for/next/topic3 (proc-receive failed to report status)\n \tEOF\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nindex 5d021a4837..e4baa13ea3 100644\n--- a/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -60,7 +60,7 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcel\n \t*    HEAD:refs/heads/baz    [new branch]\n \t*    HEAD:refs/for/next/topic2    [new reference]\n \t*    HEAD:refs/heads/foo    [new branch]\n-\t*    HEAD:refs/for/master/topic    [new reference]\n+\t     HEAD:refs/for/master/topic    <OID-A>..<OID-B>\n \t!    HEAD:refs/for/next/topic1    [remote rejected] (fail to call Web API)\n \t!    HEAD:refs/for/next/topic3    [remote rejected] (proc-receive failed to report status)\n \tDone\ndiff --git a/transport-helper.c b/transport-helper.c\nindex defafbf4c1..9e55bb89f9 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -723,13 +723,61 @@ static int fetch(struct transport *transport,\n \treturn -1;\n }\n \n+struct push_update_ref_state {\n+\tstruct ref *hint;\n+\tstruct ref_push_report *report;\n+\tint new_report;\n+};\n+\n static int push_update_ref_status(struct strbuf *buf,\n-\t\t\t\t   struct ref **ref,\n+\t\t\t\t   struct push_update_ref_state *state,\n \t\t\t\t   struct ref *remote_refs)\n {\n \tchar *refname, *msg;\n \tint status, forced = 0;\n \n+\tif (starts_with(buf->buf, \"option \")) {\n+\t\tstruct object_id old_oid, new_oid;\n+\t\tconst char *key, *val;\n+\t\tchar *p;\n+\n+\t\tif (!state->hint || !(state->report || state->new_report))\n+\t\t\tdie(_(\"'option' without a matching 'ok/error' directive\"));\n+\t\tif (state->new_report) {\n+\t\t\tif (!state->hint->report) {\n+\t\t\t\tstate->hint->report = xcalloc(1, sizeof(struct ref_push_report));\n+\t\t\t\tstate->report = state->hint->report;\n+\t\t\t} else {\n+\t\t\t\tstate->report = state->hint->report;\n+\t\t\t\twhile (state->report->next)\n+\t\t\t\t\tstate->report = state->report->next;\n+\t\t\t\tstate->report->next = xcalloc(1, sizeof(struct ref_push_report));\n+\t\t\t\tstate->report = state->report->next;\n+\t\t\t}\n+\t\t\tstate->new_report = 0;\n+\t\t}\n+\t\tkey = buf->buf + 7;\n+\t\tp = strchr(key, ' ');\n+\t\tif (p)\n+\t\t\t*p++ = '\\0';\n+\t\tval = p;\n+\t\tif (!strcmp(key, \"refname\"))\n+\t\t\tstate->report->ref_name = xstrdup_or_null(val);\n+\t\telse if (!strcmp(key, \"old-oid\") && val &&\n+\t\t\t !parse_oid_hex(val, &old_oid, &val))\n+\t\t\tstate->report->old_oid = oiddup(&old_oid);\n+\t\telse if (!strcmp(key, \"new-oid\") && val &&\n+\t\t\t !parse_oid_hex(val, &new_oid, &val))\n+\t\t\tstate->report->new_oid = oiddup(&new_oid);\n+\t\telse if (!strcmp(key, \"forced-update\"))\n+\t\t\tstate->report->forced_update = 1;\n+\t\t/* Not update remote namespace again. */\n+\t\treturn 1;\n+\t}\n+\n+\tstate->report = NULL;\n+\tstate->new_report = 0;\n+\n \tif (starts_with(buf->buf, \"ok \")) {\n \t\tstatus = REF_STATUS_OK;\n \t\trefname = buf->buf + 3;\n@@ -785,16 +833,16 @@ static int push_update_ref_status(struct strbuf *buf,\n \t\t}\n \t}\n \n-\tif (*ref)\n-\t\t*ref = find_ref_by_name(*ref, refname);\n-\tif (!*ref)\n-\t\t*ref = find_ref_by_name(remote_refs, refname);\n-\tif (!*ref) {\n+\tif (state->hint)\n+\t\tstate->hint = find_ref_by_name(state->hint, refname);\n+\tif (!state->hint)\n+\t\tstate->hint = find_ref_by_name(remote_refs, refname);\n+\tif (!state->hint) {\n \t\twarning(_(\"helper reported unexpected status of %s\"), refname);\n \t\treturn 1;\n \t}\n \n-\tif ((*ref)->status != REF_STATUS_NONE) {\n+\tif (state->hint->status != REF_STATUS_NONE) {\n \t\t/*\n \t\t * Earlier, the ref was marked not to be pushed, so ignore the ref\n \t\t * status reported by the remote helper if the latter is 'no match'.\n@@ -803,9 +851,11 @@ static int push_update_ref_status(struct strbuf *buf,\n \t\t\treturn 1;\n \t}\n \n-\t(*ref)->status = status;\n-\t(*ref)->forced_update |= forced;\n-\t(*ref)->remote_status = msg;\n+\tif (status == REF_STATUS_OK)\n+\t\tstate->new_report = 1;\n+\tstate->hint->status = status;\n+\tstate->hint->forced_update |= forced;\n+\tstate->hint->remote_status = msg;\n \treturn !(status == REF_STATUS_OK);\n }\n \n@@ -813,37 +863,57 @@ static int push_update_refs_status(struct helper_data *data,\n \t\t\t\t    struct ref *remote_refs,\n \t\t\t\t    int flags)\n {\n+\tstruct ref *ref;\n+\tstruct ref_push_report *report;\n \tstruct strbuf buf = STRBUF_INIT;\n-\tstruct ref *ref = remote_refs;\n-\tint ret = 0;\n+\tstruct push_update_ref_state state = { remote_refs, NULL, 0 };\n \n \tfor (;;) {\n-\t\tchar *private;\n-\n \t\tif (recvline(data, &buf)) {\n-\t\t\tret = 1;\n-\t\t\tbreak;\n+\t\t\tstrbuf_release(&buf);\n+\t\t\treturn 1;\n \t\t}\n-\n \t\tif (!buf.len)\n \t\t\tbreak;\n+\t\tpush_update_ref_status(&buf, &state, remote_refs);\n+\t}\n+\tstrbuf_release(&buf);\n \n-\t\tif (push_update_ref_status(&buf, &ref, remote_refs))\n-\t\t\tcontinue;\n+\tif (flags & TRANSPORT_PUSH_DRY_RUN || !data->rs.nr || data->no_private_update)\n+\t\treturn 0;\n \n-\t\tif (flags & TRANSPORT_PUSH_DRY_RUN || !data->rs.nr || data->no_private_update)\n-\t\t\tcontinue;\n+\t/* propagate back the update to the remote namespace */\n+\tfor (ref = remote_refs; ref; ref = ref->next) {\n+\t\tchar *private;\n \n-\t\t/* propagate back the update to the remote namespace */\n-\t\tprivate = apply_refspecs(&data->rs, ref->name);\n-\t\tif (!private)\n+\t\tif (ref->status != REF_STATUS_OK)\n \t\t\tcontinue;\n-\t\tupdate_ref(\"update by helper\", private, &ref->new_oid, NULL,\n-\t\t\t   0, 0);\n-\t\tfree(private);\n+\n+\t\tif (!ref->report) {\n+\t\t\tprivate = apply_refspecs(&data->rs, ref->name);\n+\t\t\tif (!private)\n+\t\t\t\tcontinue;\n+\t\t\tupdate_ref(\"update by helper\", private, &(ref->new_oid),\n+\t\t\t\t   NULL, 0, 0);\n+\t\t\tfree(private);\n+\t\t} else {\n+\t\t\tfor (report = ref->report; report; report = report->next) {\n+\t\t\t\tprivate = apply_refspecs(&data->rs,\n+\t\t\t\t\t\t\t report->ref_name\n+\t\t\t\t\t\t\t ? report->ref_name\n+\t\t\t\t\t\t\t : ref->name);\n+\t\t\t\tif (!private)\n+\t\t\t\t\tcontinue;\n+\t\t\t\tupdate_ref(\"update by helper\", private,\n+\t\t\t\t\t   report->new_oid\n+\t\t\t\t\t   ? report->new_oid\n+\t\t\t\t\t   : &(ref->new_oid),\n+\t\t\t\t\t   NULL, 0, 0);\n+\t\t\t\tfree(private);\n+\t\t\t}\n+\t\t}\n \t}\n-\tstrbuf_release(&buf);\n-\treturn ret;\n+\treturn 0;\n }\n \n static void set_common_push_options(struct transport *transport,\ndiff --git a/transport.c b/transport.c\nindex 84ec80447b..75e97a0c20 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -461,13 +461,21 @@ void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int v\n \n static void print_ref_status(char flag, const char *summary,\n \t\t\t     struct ref *to, struct ref *from, const char *msg,\n+\t\t\t     struct ref_push_report *report,\n \t\t\t     int porcelain, int summary_width)\n {\n+\tconst char *to_name;\n+\n+\tif (report && report->ref_name)\n+\t\tto_name = report->ref_name;\n+\telse\n+\t\tto_name = to->name;\n+\n \tif (porcelain) {\n \t\tif (from)\n-\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t%s:%s\\t\", flag, from->name, to_name);\n \t\telse\n-\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to->name);\n+\t\t\tfprintf(stdout, \"%c\\t:%s\\t\", flag, to_name);\n \t\tif (msg)\n \t\t\tfprintf(stdout, \"%s (%s)\\n\", summary, msg);\n \t\telse\n@@ -481,9 +489,11 @@ static void print_ref_status(char flag, const char *summary,\n \t\tfprintf(stderr, \" %s%c %-*s%s \", red, flag, summary_width,\n \t\t\tsummary, reset);\n \t\tif (from)\n-\t\t\tfprintf(stderr, \"%s -> %s\", prettify_refname(from->name), prettify_refname(to->name));\n+\t\t\tfprintf(stderr, \"%s -> %s\",\n+\t\t\t\tprettify_refname(from->name),\n+\t\t\t\tprettify_refname(to_name));\n \t\telse\n-\t\t\tfputs(prettify_refname(to->name), stderr);\n+\t\t\tfputs(prettify_refname(to_name), stderr);\n \t\tif (msg) {\n \t\t\tfputs(\" (\", stderr);\n \t\t\tfputs(msg, stderr);\n@@ -493,27 +503,52 @@ static void print_ref_status(char flag, const char *summary,\n \t}\n }\n \n-static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_width)\n+static void print_ok_ref_status(struct ref *ref,\n+\t\t\t\tstruct ref_push_report *report,\n+\t\t\t\tint porcelain, int summary_width)\n {\n+\tstruct object_id *old_oid;\n+\tstruct object_id *new_oid;\n+\tconst char *ref_name;\n+\tint forced_update;\n+\n+\tif (report && report->old_oid)\n+\t\told_oid = report->old_oid;\n+\telse\n+\t\told_oid = &ref->old_oid;\n+\tif (report && report->new_oid)\n+\t\tnew_oid = report->new_oid;\n+\telse\n+\t\tnew_oid = &ref->new_oid;\n+\tif (report && report->forced_update)\n+\t\tforced_update = report->forced_update;\n+\telse\n+\t\tforced_update = ref->forced_update;\n+\tif (report && report->ref_name)\n+\t\tref_name = report->ref_name;\n+\telse\n+\t\tref_name = ref->name;\n+\n \tif (ref->deletion)\n \t\tprint_ref_status('-', \"[deleted]\", ref, NULL, NULL,\n-\t\t\t\t porcelain, summary_width);\n-\telse if (is_null_oid(&ref->old_oid))\n+\t\t\t\t report, porcelain, summary_width);\n+\telse if (is_null_oid(old_oid))\n \t\tprint_ref_status('*',\n-\t\t\t\t (starts_with(ref->name, \"refs/tags/\")\n+\t\t\t\t (starts_with(ref_name, \"refs/tags/\")\n \t\t\t\t  ? \"[new tag]\"\n-\t\t\t\t  : (starts_with(ref->name, \"refs/heads/\")\n+\t\t\t\t  : (starts_with(ref_name, \"refs/heads/\")\n \t\t\t\t     ? \"[new branch]\"\n \t\t\t\t     : \"[new reference]\")),\n-\t\t\t\t ref, ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t ref, ref->peer_ref, NULL,\n+\t\t\t\t report, porcelain, summary_width);\n \telse {\n \t\tstruct strbuf quickref = STRBUF_INIT;\n \t\tchar type;\n \t\tconst char *msg;\n \n-\t\tstrbuf_add_unique_abbrev(&quickref, &ref->old_oid,\n+\t\tstrbuf_add_unique_abbrev(&quickref, old_oid,\n \t\t\t\t\t DEFAULT_ABBREV);\n-\t\tif (ref->forced_update) {\n+\t\tif (forced_update) {\n \t\t\tstrbuf_addstr(&quickref, \"...\");\n \t\t\ttype = '+';\n \t\t\tmsg = \"forced update\";\n@@ -522,16 +557,17 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt\n \t\t\ttype = ' ';\n \t\t\tmsg = NULL;\n \t\t}\n-\t\tstrbuf_add_unique_abbrev(&quickref, &ref->new_oid,\n+\t\tstrbuf_add_unique_abbrev(&quickref, new_oid,\n \t\t\t\t\t DEFAULT_ABBREV);\n \n \t\tprint_ref_status(type, quickref.buf, ref, ref->peer_ref, msg,\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t report, porcelain, summary_width);\n \t\tstrbuf_release(&quickref);\n \t}\n }\n \n-static int print_one_push_status(struct ref *ref, const char *dest, int count,\n+static int print_one_push_report(struct ref *ref, const char *dest, int count,\n+\t\t\t\t struct ref_push_report *report,\n \t\t\t\t int porcelain, int summary_width)\n {\n \tif (!count) {\n@@ -543,65 +579,89 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count,\n \tswitch(ref->status) {\n \tcase REF_STATUS_NONE:\n \t\tprint_ref_status('X', \"[no match]\", ref, NULL, NULL,\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NODELETE:\n \t\tprint_ref_status('!', \"[rejected]\", ref, NULL,\n \t\t\t\t \"remote does not support deleting refs\",\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_UPTODATE:\n \t\tprint_ref_status('=', \"[up to date]\", ref,\n-\t\t\t\t ref->peer_ref, NULL, porcelain, summary_width);\n+\t\t\t\t ref->peer_ref, NULL,\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NONFASTFORWARD:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"non-fast-forward\", porcelain, summary_width);\n+\t\t\t\t \"non-fast-forward\",\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"already exists\", porcelain, summary_width);\n+\t\t\t\t \"already exists\",\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_FETCH_FIRST:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"fetch first\", porcelain, summary_width);\n+\t\t\t\t \"fetch first\",\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_NEEDS_FORCE:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"needs force\", porcelain, summary_width);\n+\t\t\t\t \"needs force\",\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_STALE:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"stale info\", porcelain, summary_width);\n+\t\t\t\t \"stale info\",\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REJECT_SHALLOW:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n \t\t\t\t \"new shallow roots not allowed\",\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_REMOTE_REJECT:\n \t\tprint_ref_status('!', \"[remote rejected]\", ref,\n \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n-\t\t\t\t ref->remote_status, porcelain, summary_width);\n+\t\t\t\t ref->remote_status,\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_EXPECTING_REPORT:\n \t\tprint_ref_status('!', \"[remote failure]\", ref,\n \t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n \t\t\t\t \"remote failed to report status\",\n-\t\t\t\t porcelain, summary_width);\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_ATOMIC_PUSH_FAILED:\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n-\t\t\t\t \"atomic push failed\", porcelain, summary_width);\n+\t\t\t\t \"atomic push failed\",\n+\t\t\t\t report, porcelain, summary_width);\n \t\tbreak;\n \tcase REF_STATUS_OK:\n-\t\tprint_ok_ref_status(ref, porcelain, summary_width);\n+\t\tprint_ok_ref_status(ref, report, porcelain, summary_width);\n \t\tbreak;\n \t}\n \n \treturn 1;\n }\n \n+static int print_one_push_status(struct ref *ref, const char *dest, int count,\n+\t\t\t\t int porcelain, int summary_width)\n+{\n+\tstruct ref_push_report *report;\n+\tint n = 0;\n+\n+\tif (!ref->report)\n+\t\treturn print_one_push_report(ref, dest, count,\n+\t\t\t\t\t     NULL, porcelain, summary_width);\n+\n+\tfor (report = ref->report; report; report = report->next)\n+\t\tprint_one_push_report(ref, dest, count + n++,\n+\t\t\t\t      report, porcelain, summary_width);\n+\treturn n;\n+}\n+\n static int measure_abbrev(const struct object_id *oid, int sofar)\n {\n \tchar hex[GIT_MAX_HEXSZ + 1];\n-- \n2.26.2.543.g44b58e439b\n\n"},{"id":"404614","messageId":"20200827154551.5966-9-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200824174202.11710-1-worldhello.net@gmail.com","subject":"[PATCH v19 08/10] t5411: test updates of remote-tracking branches","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-27T15:45:49Z","receivedAt":"2020-08-27T15:46:17Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nIn order to test update of remote-tracking branches for special refs,\nadd new \"remote.origin.fetch\" settings and test cases.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 48 +++++++++++++++++++\n 1 file changed, 48 insertions(+)\n\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex 27d58edd14..8eec3a1ed8 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -1,3 +1,18 @@\n+test_expect_success \"setup git config for remote-tracking of special refs\" '\n+\t(\n+\t\tcd workbench &&\n+\t\tif ! git config --get-all remote.origin.fetch | grep refs/for/\n+\t\tthen\n+\t\t\tgit config --add remote.origin.fetch \\\n+\t\t\t\t\"+refs/for/*:refs/t/for/*\" &&\n+\t\t\tgit config --add remote.origin.fetch \\\n+\t\t\t\t\"+refs/pull/*:refs/t/pull/*\" &&\n+\t\t\tgit config --add remote.origin.fetch \\\n+\t\t\t\t\"+refs/changes/*:refs/t/changes/*\"\n+\t\tfi\n+\t)\n+'\n+\n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL)\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n@@ -58,6 +73,17 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \ttest_cmp expect actual\n '\n \n+test_expect_success \"proc-receive: check remote-tracking #1 ($PROTOCOL)\" '\n+\tgit -C workbench show-ref |\n+\t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/t/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C workbench update-ref -d refs/t/for/master/topic\n+'\n+\n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n@@ -120,6 +146,17 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \ttest_cmp expect actual\n '\n \n+test_expect_success \"proc-receive: check remote-tracking #2 ($PROTOCOL)\" '\n+\tgit -C workbench show-ref |\n+\t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/t/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C workbench update-ref -d refs/t/for/master/topic\n+'\n+\n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n@@ -167,3 +204,14 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n \tEOF\n \ttest_cmp expect actual\n '\n+\n+test_expect_success \"proc-receive: check remote-tracking #3 ($PROTOCOL)\" '\n+\tgit -C workbench show-ref |\n+\t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/t/for/master/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C workbench update-ref -d refs/t/for/master/topic\n+'\n-- \n2.26.2.543.g44b58e439b\n\n"},{"id":"404617","messageId":"20200827154551.5966-10-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200824174202.11710-1-worldhello.net@gmail.com","subject":"[PATCH v19 09/10] transport: parse report options for tracking refs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-27T15:45:50Z","receivedAt":"2020-08-27T15:46:20Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nWhen pushing a pseudo reference (such as \"refs/for/master/topic\"), may\ncreate or update one or more references.  The real names of the\nreferences will be stored in the report options.  Parse report options\nto create or update remote-tracking branches properly.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 22 ++++++++---\n transport.c                                   | 37 +++++++++++++++----\n 2 files changed, 45 insertions(+), 14 deletions(-)\n\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex 8eec3a1ed8..73283d81e8 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -78,10 +78,14 @@ test_expect_success \"proc-receive: check remote-tracking #1 ($PROTOCOL)\" '\n \t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n-\t<COMMIT-A> refs/t/for/master/topic\n+\t<COMMIT-A> refs/t/changes/24/124/1\n+\t<COMMIT-B> refs/t/changes/25/125/1\n+\t<COMMIT-B> refs/t/for/master/topic\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C workbench update-ref -d refs/t/for/master/topic\n+\tgit -C workbench update-ref -d refs/t/for/master/topic &&\n+\tgit -C workbench update-ref -d refs/t/changes/24/124/1 &&\n+\tgit -C workbench update-ref -d refs/t/changes/25/125/1\n '\n \n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)\" '\n@@ -151,10 +155,14 @@ test_expect_success \"proc-receive: check remote-tracking #2 ($PROTOCOL)\" '\n \t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n-\t<COMMIT-A> refs/t/for/master/topic\n+\t<COMMIT-A> refs/t/changes/24/124/1\n+\t<COMMIT-A> refs/t/changes/25/125/1\n+\t<COMMIT-B> refs/t/for/master/topic\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C workbench update-ref -d refs/t/for/master/topic\n+\tgit -C workbench update-ref -d refs/t/for/master/topic &&\n+\tgit -C workbench update-ref -d refs/t/changes/24/124/1 &&\n+\tgit -C workbench update-ref -d refs/t/changes/25/125/1\n '\n \n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)\" '\n@@ -210,8 +218,10 @@ test_expect_success \"proc-receive: check remote-tracking #3 ($PROTOCOL)\" '\n \t\tgrep -v -e refs/remotes -e refs/heads -e refs/tags >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n-\t<COMMIT-A> refs/t/for/master/topic\n+\t<COMMIT-A> refs/t/changes/23/123/1\n+\t<COMMIT-B> refs/t/changes/24/124/2\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C workbench update-ref -d refs/t/for/master/topic\n+\tgit -C workbench update-ref -d refs/t/changes/24/124/1 &&\n+\tgit -C workbench update-ref -d refs/t/changes/25/125/2\n '\ndiff --git a/transport.c b/transport.c\nindex 75e97a0c20..213ec69d7a 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -436,29 +436,50 @@ int transport_refs_pushed(struct ref *ref)\n \treturn 0;\n }\n \n-void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose)\n+static void update_one_tracking_ref(struct remote *remote, char *refname,\n+\t\t\t\t    struct object_id *new_oid, int deletion,\n+\t\t\t\t    int verbose)\n {\n \tstruct refspec_item rs;\n \n-\tif (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE)\n-\t\treturn;\n-\n \tmemset(&rs, 0, sizeof(rs));\n-\trs.src = ref->name;\n+\trs.src = refname;\n \trs.dst = NULL;\n \n \tif (!remote_find_tracking(remote, &rs)) {\n \t\tif (verbose)\n \t\t\tfprintf(stderr, \"updating local tracking ref '%s'\\n\", rs.dst);\n-\t\tif (ref->deletion) {\n+\t\tif (deletion)\n \t\t\tdelete_ref(NULL, rs.dst, NULL, 0);\n-\t\t} else\n-\t\t\tupdate_ref(\"update by push\", rs.dst, &ref->new_oid,\n+\t\telse\n+\t\t\tupdate_ref(\"update by push\", rs.dst, new_oid,\n \t\t\t\t   NULL, 0, 0);\n \t\tfree(rs.dst);\n \t}\n }\n \n+void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose)\n+{\n+\tchar *refname;\n+\tstruct object_id *new_oid;\n+\tstruct ref_push_report *report;\n+\n+\tif (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE)\n+\t\treturn;\n+\n+\treport = ref->report;\n+\tif (!report)\n+\t\tupdate_one_tracking_ref(remote, ref->name, &ref->new_oid,\n+\t\t\t\t\tref->deletion, verbose);\n+\telse\n+\t\tfor (; report; report = report->next) {\n+\t\t\trefname = report->ref_name ? (char *)report->ref_name : ref->name;\n+\t\t\tnew_oid = report->new_oid ? report->new_oid : &ref->new_oid;\n+\t\t\tupdate_one_tracking_ref(remote, refname, new_oid,\n+\t\t\t\t\t\tis_null_oid(new_oid), verbose);\n+\t\t}\n+}\n+\n static void print_ref_status(char flag, const char *summary,\n \t\t\t     struct ref *to, struct ref *from, const char *msg,\n \t\t\t     struct ref_push_report *report,\n-- \n2.26.2.543.g44b58e439b\n\n"},{"id":"404618","messageId":"20200827154551.5966-7-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200824174202.11710-1-worldhello.net@gmail.com","subject":"[PATCH v19 06/10] doc: add document for capability report-status-v2","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-27T15:45:47Z","receivedAt":"2020-08-27T15:46:21Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd ABNF notation for capability 'report-status-v2' which extends\ncapability 'report-status' by adding additional option lines.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/technical/pack-protocol.txt     | 41 +++++++++++++++++--\n .../technical/protocol-capabilities.txt       | 17 ++++++--\n 2 files changed, 52 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt\nindex a4573d12ce..e13a2c064d 100644\n--- a/Documentation/technical/pack-protocol.txt\n+++ b/Documentation/technical/pack-protocol.txt\n@@ -503,8 +503,8 @@ The reference discovery phase is done nearly the same way as it is in the\n fetching protocol. Each reference obj-id and name on the server is sent\n in packet-line format to the client, followed by a flush-pkt.  The only\n real difference is that the capability listing is different - the only\n-possible values are 'report-status', 'delete-refs', 'ofs-delta' and\n-'push-options'.\n+possible values are 'report-status', 'report-status-v2', 'delete-refs',\n+'ofs-delta', 'atomic' and 'push-options'.\n \n Reference Update Request and Packfile Transfer\n ----------------------------------------------\n@@ -625,7 +625,7 @@ Report Status\n -------------\n \n After receiving the pack data from the sender, the receiver sends a\n-report if 'report-status' capability is in effect.\n+report if 'report-status' or 'report-status-v2' capability is in effect.\n It is a short listing of what happened in that update.  It will first\n list the status of the packfile unpacking as either 'unpack ok' or\n 'unpack [error]'.  Then it will list the status for each of the references\n@@ -647,6 +647,41 @@ update was successful, or 'ng [refname] [error]' if the update was not.\n   error-msg         = 1*(OCTET) ; where not \"ok\"\n ----\n \n+The 'report-status-v2' capability extends the protocol by adding new option\n+lines in order to support reporting of reference rewritten by the\n+'proc-receive' hook.  The 'proc-receive' hook may handle a command for a\n+pseudo-reference which may create or update one or more references, and each\n+reference may have different name, different new-oid, and different old-oid.\n+\n+----\n+  report-status-v2  = unpack-status\n+\t\t      1*(command-status-v2)\n+\t\t      flush-pkt\n+\n+  unpack-status     = PKT-LINE(\"unpack\" SP unpack-result)\n+  unpack-result     = \"ok\" / error-msg\n+\n+  command-status-v2 = command-ok-v2 / command-fail\n+  command-ok-v2     = command-ok\n+\t\t      *option-line\n+\n+  command-ok        = PKT-LINE(\"ok\" SP refname)\n+  command-fail      = PKT-LINE(\"ng\" SP refname SP error-msg)\n+\n+  error-msg         = 1*(OCTET) ; where not \"ok\"\n+\n+  option-line       = *1(option-refname)\n+\t\t      *1(option-old-oid)\n+\t\t      *1(option-new-oid)\n+\t\t      *1(option-forced-update)\n+\n+  option-refname    = PKT-LINE(\"option\" SP \"refname\" SP refname)\n+  option-old-oid    = PKT-LINE(\"option\" SP \"old-oid\" SP obj-id)\n+  option-new-oid    = PKT-LINE(\"option\" SP \"new-oid\" SP obj-id)\n+  option-force      = PKT-LINE(\"option\" SP \"forced-update\")\n+\n+----\n+\n Updates can be unsuccessful for a number of reasons.  The reference can have\n changed since the reference discovery phase was originally sent, meaning\n someone pushed in the meantime.  The reference being pushed could be a\ndiff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt\nindex 124d716807..ba869a7d36 100644\n--- a/Documentation/technical/protocol-capabilities.txt\n+++ b/Documentation/technical/protocol-capabilities.txt\n@@ -22,9 +22,9 @@ was sent.  Server MUST NOT ignore capabilities that client requested\n and server advertised.  As a consequence of these rules, server MUST\n NOT advertise capabilities it does not understand.\n \n-The 'atomic', 'report-status', 'delete-refs', 'quiet', and 'push-cert'\n-capabilities are sent and recognized by the receive-pack (push to server)\n-process.\n+The 'atomic', 'report-status', 'report-status-v2', 'delete-refs', 'quiet',\n+and 'push-cert' capabilities are sent and recognized by the receive-pack\n+(push to server) process.\n \n The 'ofs-delta' and 'side-band-64k' capabilities are sent and recognized\n by both upload-pack and receive-pack protocols.  The 'agent' capability\n@@ -284,6 +284,17 @@ each reference was updated successfully.  If any of those were not\n successful, it will send back an error message.  See pack-protocol.txt\n for example messages.\n \n+report-status-v2\n+----------------\n+\n+Capability 'report-status-v2' extends capability 'report-status' by\n+adding new \"option\" directives in order to support reference rewritten by\n+the \"proc-receive\" hook.  The \"proc-receive\" hook may handle a command\n+for a pseudo-reference which may create or update a reference with\n+different name, new-oid, and old-oid.  While the capability\n+'report-status' cannot report for such case.  See pack-protocol.txt\n+for details.\n+\n delete-refs\n -----------\n \n-- \n2.26.2.543.g44b58e439b\n\n"},{"id":"404619","messageId":"20200827154551.5966-11-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20200824174202.11710-1-worldhello.net@gmail.com","subject":"[PATCH v19 10/10] doc: add documentation for the proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-08-27T15:45:51Z","receivedAt":"2020-08-27T15:46:22Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\n\"git receive-pack\" that accepts requests by \"git push\" learned to\noutsource some of the ref updates to the new \"proc-receive\" hook.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n Documentation/githooks.txt | 62 ++++++++++++++++++++++++++++++++++++++\n 1 file changed, 62 insertions(+)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 31b601e4bc..2899e80445 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -333,6 +333,68 @@ The default 'update' hook, when enabled--and with\n `hooks.allowunannotated` config option unset or set to false--prevents\n unannotated tags to be pushed.\n \n+[[proc-receive]]\n+proc-receive\n+~~~~~~~~~~~~\n+\n+This hook is invoked by linkgit:git-receive-pack[1].  If the server has\n+set the multi-valued config variable `receive.procReceiveRefs`, and the\n+commands sent to 'receive-pack' have matching reference names, these\n+commands will be executed by this hook, instead of by the internal\n+`execute_commands()` function.  This hook is responsible for updating\n+the relevant references and reporting the results back to 'receive-pack'.\n+\n+This hook executes once for the receive operation.  It takes no\n+arguments, but uses a pkt-line format protocol to communicate with\n+'receive-pack' to read commands, push-options and send results.  In the\n+following example for the protocol, the letter 'S' stands for\n+'receive-pack' and the letter 'H' stands for this hook.\n+\n+    # Version and features negotiation.\n+    S: PKT-LINE(version=1\\0push-options atomic...)\n+    S: flush-pkt\n+    H: PKT-LINE(version=1\\0push-options...)\n+    H: flush-pkt\n+\n+    # Send commands from server to the hook.\n+    S: PKT-LINE(<old-oid> <new-oid> <ref>)\n+    S: ... ...\n+    S: flush-pkt\n+    # Send push-options only if the 'push-options' feature is enabled.\n+    S: PKT-LINE(push-option)\n+    S: ... ...\n+    S: flush-pkt\n+\n+    # Receive result from the hook.\n+    # OK, run this command successfully.\n+    H: PKT-LINE(ok <ref>)\n+    # NO, I reject it.\n+    H: PKT-LINE(ng <ref> <reason>)\n+    # Fall through, let 'receive-pack' to execute it.\n+    H: PKT-LINE(ok <ref>)\n+    H: PKT-LINE(option fall-through)\n+    # OK, but has an alternate reference.  The alternate reference name\n+    # and other status can be given in option directives.\n+    H: PKT-LINE(ok <ref>)\n+    H: PKT-LINE(option refname <refname>)\n+    H: PKT-LINE(option old-oid <old-oid>)\n+    H: PKT-LINE(option new-oid <new-oid>)\n+    H: PKT-LINE(option forced-update)\n+    H: ... ...\n+    H: flush-pkt\n+\n+Each command for the 'proc-receive' hook may point to a pseudo-reference\n+and always has a zero-old as its old-oid, while the 'proc-receive' hook\n+may update an alternate reference and the alternate reference may exist\n+already with a non-zero old-oid.  For this case, this hook will use\n+\"option\" directives to report extended attributes for the reference given\n+by the leading \"ok\" directive.\n+\n+The report of the commands of this hook should have the same order as\n+the input.  The exit status of the 'proc-receive' hook only determines\n+the success or failure of the group of commands sent to it, unless\n+atomic push is in use.\n+\n [[post-receive]]\n post-receive\n ~~~~~~~~~~~~\n-- \n2.26.2.543.g44b58e439b\n\n"},{"id":"404653","messageId":"xmqqmu2fq2u6.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20200827154551.5966-1-worldhello.net@gmail.com","subject":"Re: [PATCH v19 00/10] New proc-receive hook for centralized workflow","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-08-27T19:57:21Z","receivedAt":"2020-08-27T19:57:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n>\n> ## Changes since v18\n>\n> 1. This series is based on \"next\" branch, in order to resolve a conflict\n>    with commit 95e7c38539 (refspec: make sure stack refspec_item\n>    variables are zeroed, 2020-08-14).  See patch 9/10.\n\nWe cannot ever merge such a topic that depends on 'next' down to\n'master' without dragging all the other topics that present in\n'next', plus the merge commits that bring these topics to 'next'.\n\nIn this particular case, I think the conflict resolution is trivial\nand more importantly, it is not a *new* conflict v19 introduces but\ndid not exist with v18.  IOW, what is in 'seen' has already resolved\nthe same conflict between 95e7c38539 and this topic.\n\nI've applied these directly on 'next' (call it c0), rebased the\nresult on the same base as v18 is queued on, and then merged the\nrebased v19 (call it c1) with 'next' to make sure that the result\n(call it c2) matches, i.e. tree of c0 and tree of c2 are identical.\n\nI'll use c1 to replace what is queued in 'seen'.\n\nA workable alternative would be to base these on top of the topic\nthat contains 95e7c38539 (i.e. jk/refspecs-cleanup).  Then, this\ntopic is still taken hostage by that 'cleanup' topic, but at least\nas long as that topic graduates, this topic can graduate to 'master'\nwithout having to drag any other cruft with it.\n\nThanks.\n\n"},{"id":"409172","messageId":"nycvar.QRO.7.76.6.2011042236270.18437@tvgsbejvaqbjf.bet","threadId":"52936","inReplyTo":"20200827154551.5966-4-worldhello.net@gmail.com","subject":"Re: [PATCH v19 03/10] receive-pack: add new proc-receive hook","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2020-11-04T22:15:31Z","receivedAt":"2020-11-04T22:15:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Thu, 27 Aug 2020, Jiang Xin wrote:\n\n> +test_expect_success \"setup proc-receive hook (hook --die-readline, $PROTOCOL)\" '\n> +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n> +\tprintf >&2 \"# proc-receive hook\\n\"\n> +\ttest-tool proc-receive -v --die-readline\n> +\tEOF\n> +'\n> +\n> +# Refs of upstream : master(A)\n> +# Refs of workbench: master(A)  tags/v123\n> +# git push         :                       refs/for/master/topic(A)\n> +test_expect_success \"proc-receive: bad protocol (hook --die-readline, $PROTOCOL)\" '\n> +\ttest_must_fail git -C workbench push origin \\\n> +\t\tHEAD:refs/for/master/topic \\\n> +\t\t>out 2>&1 &&\n> +\tmake_user_friendly_and_stable_output <out >actual &&\n> +\n> +\tgrep \"remote: fatal: protocol error: expected \\\"old new ref\\\", got \\\"<ZERO-OID> <COMMIT-A> refs/for/master/topic\\\"\" actual &&\n> +\n> +\tgit -C \"$upstream\" show-ref >out &&\n> +\tmake_user_friendly_and_stable_output <out >actual &&\n> +\tcat >expect <<-EOF &&\n> +\t<COMMIT-A> refs/heads/master\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n\nWhile investigating a flaky hang in t5411 in the osx-clang job of our\nCI/PR builds, I ran into this issue (with --stress):\n\n-- snip --\n[...]\n+++ test_must_fail git -C workbench push origin HEAD:refs/for/main/topic\n+++ case \"$1\" in\n+++ _test_ok=\n+++ test_must_fail_acceptable git -C workbench push origin HEAD:refs/for/main/topic\n+++ test git = env\n+++ case \"$1\" in\n+++ return 0\n+++ git -C workbench push origin HEAD:refs/for/main/topic\n+++ exit_code=128\n+++ test 128 -eq 0\n+++ test_match_signal 13 128\n+++ test 128 = 141\n+++ test 128 = 269\n+++ return 1\n+++ test 128 -gt 129\n+++ test 128 -eq 127\n+++ test 128 -eq 126\n+++ return 0\n+++ make_user_friendly_and_stable_output\n++++ echo 4844d8acf2fc62b151a29cfb3b916b6778476b9e\n++++ cut -c1-7\n++++ echo c208c3bb8a1aeea6d4e40027a17251ccd59a2b1b\n++++ cut -c1-7\n+++ sed -e 's/  *$//' -e 's/   */ /g' -e 's/'\\''/\"/g' -e 's/    /    /g' -e 's/4844d8acf2fc62b151a29cfb3b916b6778476b9e/<COMMIT-A>/g' -e 's/c208c3bb8a1aeea6d4e40027a17251ccd59a2b1b/<COMMIT-B>/g' -e 's/f7cc02ec1b3393b9a9638a0e02a7039c234cffa4/<TAG-v123>/g' -e 's/0000000000000000000000000000000000000000/<ZERO-OID>/g' -e 's/4844d8a[0-9a-f]*/<OID-A>/g' -e 's/c208c3b[0-9a-f]*/<OID-B>/g' -e 's#To \\.\\./upstream.git#To <URL/of/upstream.git>#' -e '/^error: / d'\n+++ grep 'remote: fatal: protocol error: expected \"old new ref\", got \"<ZERO-OID> <COMMIT-A> refs/for/main/topic\"' actual\nerror: last command exited with $?=1\nnot ok 34 - proc-receive: bad protocol (hook --die-readline, builtin protocol)\n#\n#               test_must_fail git -C workbench push origin \\\n#                       HEAD:refs/for/main/topic \\\n#                       >out 2>&1 &&\n#               make_user_friendly_and_stable_output <out >actual &&\n#\n#               grep \"remote: fatal: protocol error: expected \\\"old new\n#               ref\\\", got \\\"<ZERO-OID> <COMMIT-A> refs/for/main/topic\\\"\"\n#               actual &&\n#\n#               git -C \"$upstream\" show-ref >out &&\n#               make_user_friendly_and_stable_output <out >actual &&\n#               cat >expect <<-EOF &&\n#               <COMMIT-A> refs/heads/main\n#               EOF\n#               test_cmp expect actual\n#\n-- snap --\n\nThe output of `actual` reads like this:\n\n-- snip --\nremote: # pre-receive hook\nremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\nremote: # proc-receive hook\nfatal: unable to write flush packet: Broken pipe\nsend-pack: unexpected disconnect while reading sideband packet\nfatal: the remote end hung up unexpectedly\n-- snap --\n\nApplying Gábor's patch as obtained from\nhttps://lore.kernel.org/git/20190830121005.GI8571@szeder.dev/ seemed to\nhelp this issue at first, but then turned out not to prevent the same\nissue from happening again.\n\nAny ideas?\n\nCiao,\nJohannes\n"},{"id":"409175","messageId":"nycvar.QRO.7.76.6.2011042352040.18437@tvgsbejvaqbjf.bet","threadId":"52936","inReplyTo":"nycvar.QRO.7.76.6.2011042236270.18437@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v19 03/10] receive-pack: add new proc-receive hook","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2020-11-04T22:58:21Z","receivedAt":"2020-11-04T22:58:37Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Me again,\n\nOn Wed, 4 Nov 2020, Johannes Schindelin wrote:\n\n> On Thu, 27 Aug 2020, Jiang Xin wrote:\n>\n> > +test_expect_success \"setup proc-receive hook (hook --die-readline, $PROTOCOL)\" '\n> > +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n> > +\tprintf >&2 \"# proc-receive hook\\n\"\n> > +\ttest-tool proc-receive -v --die-readline\n> > +\tEOF\n> > +'\n> > +\n> > +# Refs of upstream : master(A)\n> > +# Refs of workbench: master(A)  tags/v123\n> > +# git push         :                       refs/for/master/topic(A)\n> > +test_expect_success \"proc-receive: bad protocol (hook --die-readline, $PROTOCOL)\" '\n> > +\ttest_must_fail git -C workbench push origin \\\n> > +\t\tHEAD:refs/for/master/topic \\\n> > +\t\t>out 2>&1 &&\n> > +\tmake_user_friendly_and_stable_output <out >actual &&\n> > +\n> > +\tgrep \"remote: fatal: protocol error: expected \\\"old new ref\\\", got \\\"<ZERO-OID> <COMMIT-A> refs/for/master/topic\\\"\" actual &&\n> > +\n> > +\tgit -C \"$upstream\" show-ref >out &&\n> > +\tmake_user_friendly_and_stable_output <out >actual &&\n> > +\tcat >expect <<-EOF &&\n> > +\t<COMMIT-A> refs/heads/master\n> > +\tEOF\n> > +\ttest_cmp expect actual\n> > +'\n>\n> While investigating a flaky hang in t5411 in the osx-clang job of our\n> CI/PR builds, I ran into this issue (with --stress):\n>\n> -- snip --\n> [...]\n> +++ test_must_fail git -C workbench push origin HEAD:refs/for/main/topic\n> +++ case \"$1\" in\n> +++ _test_ok=\n> +++ test_must_fail_acceptable git -C workbench push origin HEAD:refs/for/main/topic\n> +++ test git = env\n> +++ case \"$1\" in\n> +++ return 0\n> +++ git -C workbench push origin HEAD:refs/for/main/topic\n> +++ exit_code=128\n> +++ test 128 -eq 0\n> +++ test_match_signal 13 128\n> +++ test 128 = 141\n> +++ test 128 = 269\n> +++ return 1\n> +++ test 128 -gt 129\n> +++ test 128 -eq 127\n> +++ test 128 -eq 126\n> +++ return 0\n> +++ make_user_friendly_and_stable_output\n> ++++ echo 4844d8acf2fc62b151a29cfb3b916b6778476b9e\n> ++++ cut -c1-7\n> ++++ echo c208c3bb8a1aeea6d4e40027a17251ccd59a2b1b\n> ++++ cut -c1-7\n> +++ sed -e 's/  *$//' -e 's/   */ /g' -e 's/'\\''/\"/g' -e 's/    /    /g' -e 's/4844d8acf2fc62b151a29cfb3b916b6778476b9e/<COMMIT-A>/g' -e 's/c208c3bb8a1aeea6d4e40027a17251ccd59a2b1b/<COMMIT-B>/g' -e 's/f7cc02ec1b3393b9a9638a0e02a7039c234cffa4/<TAG-v123>/g' -e 's/0000000000000000000000000000000000000000/<ZERO-OID>/g' -e 's/4844d8a[0-9a-f]*/<OID-A>/g' -e 's/c208c3b[0-9a-f]*/<OID-B>/g' -e 's#To \\.\\./upstream.git#To <URL/of/upstream.git>#' -e '/^error: / d'\n> +++ grep 'remote: fatal: protocol error: expected \"old new ref\", got \"<ZERO-OID> <COMMIT-A> refs/for/main/topic\"' actual\n> error: last command exited with $?=1\n> not ok 34 - proc-receive: bad protocol (hook --die-readline, builtin protocol)\n> #\n> #               test_must_fail git -C workbench push origin \\\n> #                       HEAD:refs/for/main/topic \\\n> #                       >out 2>&1 &&\n> #               make_user_friendly_and_stable_output <out >actual &&\n> #\n> #               grep \"remote: fatal: protocol error: expected \\\"old new\n> #               ref\\\", got \\\"<ZERO-OID> <COMMIT-A> refs/for/main/topic\\\"\"\n> #               actual &&\n> #\n> #               git -C \"$upstream\" show-ref >out &&\n> #               make_user_friendly_and_stable_output <out >actual &&\n> #               cat >expect <<-EOF &&\n> #               <COMMIT-A> refs/heads/main\n> #               EOF\n> #               test_cmp expect actual\n> #\n> -- snap --\n>\n> The output of `actual` reads like this:\n>\n> -- snip --\n> remote: # pre-receive hook\n> remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n> remote: # proc-receive hook\n> fatal: unable to write flush packet: Broken pipe\n> send-pack: unexpected disconnect while reading sideband packet\n> fatal: the remote end hung up unexpectedly\n> -- snap --\n>\n> Applying Gábor's patch as obtained from\n> https://lore.kernel.org/git/20190830121005.GI8571@szeder.dev/ seemed to\n> help this issue at first, but then turned out not to prevent the same\n> issue from happening again.\n>\n> Any ideas?\n\nMore puzzles. I was finally able to reproduce the hang in a `--stress`\nrun. It basically happens in t5411.186, which is stalled at this:\n\n-- snip --\n[...]\nexpecting success of 5411.186 'proc-receive: bad protocol (hook --die-readline, HTTP protocol)':\n        test_must_fail git -C workbench push origin \\\n                HEAD:refs/for/main/topic \\\n                >out 2>&1 &&\n        make_user_friendly_and_stable_output <out >actual &&\n\n        grep \"remote: fatal: protocol error: expected \\\"old new ref\\\", got \\\"<ZERO-OID> <COMMIT-A> refs/for/main/topic\\\"\" actual &&\n\n        git -C \"$upstream\" show-ref >out &&\n        make_user_friendly_and_stable_output <out >actual &&\n        cat >expect <<-EOF &&\n        <COMMIT-A> refs/heads/main\n        EOF\n        test_cmp expect actual\n\n+++ test_must_fail git -C workbench push origin HEAD:refs/for/main/topic\n+++ case \"$1\" in\n+++ _test_ok=\n+++ test_must_fail_acceptable git -C workbench push origin HEAD:refs/for/main/topic\n+++ test git = env\n+++ case \"$1\" in\n+++ return 0\n+++ git -C workbench push origin HEAD:refs/for/main/topic\n-- snap --\n\nThat `push` never goes anywhere. And I found something of a clue at\n`trash directory.t5411-proc-receive-hook.stress-*/httpd/error.log`:\n\n-- snip --\n[...]\n[Wed Nov 04 22:28:20.838388 2020] [cgi:error] [pid 60577] [client 127.0.0.1:52794] AH01215: fatal: unable to write flush packet: Broken pipe: /Users/runner/work/git/git/git-http-backend\n-- snap --\n\nThis is once again running _with_ Gábor's patch mentioned in the quoted\npart above.\n\nCould you please look into this?\n\nCiao,\nJohannes\n"},{"id":"409202","messageId":"CANYiYbFcXN_ARmZ70GQkrQ=uFaf7YPObYcopEOFseON6TQzNpQ@mail.gmail.com","threadId":"52936","inReplyTo":"nycvar.QRO.7.76.6.2011042236270.18437@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v19 03/10] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-11-05T14:54:05Z","receivedAt":"2020-11-05T14:54:19Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> 于2020年11月5日周四 上午6:15写道：\n> not ok 34 - proc-receive: bad protocol (hook --die-readline, builtin protocol)\n> #\n> #               test_must_fail git -C workbench push origin \\\n> #                       HEAD:refs/for/main/topic \\\n> #                       >out 2>&1 &&\n> #               make_user_friendly_and_stable_output <out >actual &&\n> #\n> #               grep \"remote: fatal: protocol error: expected \\\"old new\n> #               ref\\\", got \\\"<ZERO-OID> <COMMIT-A> refs/for/main/topic\\\"\"\n> #               actual &&\n> #\n> #               git -C \"$upstream\" show-ref >out &&\n> #               make_user_friendly_and_stable_output <out >actual &&\n> #               cat >expect <<-EOF &&\n> #               <COMMIT-A> refs/heads/main\n> #               EOF\n> #               test_cmp expect actual\n> #\n> -- snap --\n>\n> The output of `actual` reads like this:\n>\n> -- snip --\n> remote: # pre-receive hook\n> remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n> remote: # proc-receive hook\n> fatal: unable to write flush packet: Broken pipe\n> send-pack: unexpected disconnect while reading sideband packet\n> fatal: the remote end hung up unexpectedly\n> -- snap --\n\nIn this test case, the \"proc-receive\" hook sends an error message and\ndies earlier. While \"receive-pack\" on the other side of the pipe\nshould forward the error message of the \"proc-receive\" hook to the\nclient side, but there is no such error message in output. It seems\nthat the expected error message is overridden by the broken pipe error\nmessage. The broken pipe error is because \"receive-pack\" sends other\ncommands to the \"proc-receive\" hook, but the hook dies earlier.\n\n> Applying Gábor's patch as obtained from\n> https://lore.kernel.org/git/20190830121005.GI8571@szeder.dev/ seemed to\n> help this issue at first, but then turned out not to prevent the same\n> issue from happening again.\n\nI think this is the right way to fix this issue, even though I cannot\nreproduce this issue in my laptop.\n\n1. In the `run_proc_receive_hook()` function of \"receive-pack\", should\nclose the input (proc.in) before reading result from \"proc-receive\":\n\n--  snip --\n@@ -1196,11 +1197,12 @@ static int run_proc_receive_hook(struct\ncommand *commands,\n                packet_flush(proc.in);\n        }\n\n+       close(proc.in);\n+\n        /* Read result from proc-receive */\n        code = read_proc_receive_report(&reader, commands, &errmsg);\n\n cleanup:\n-       close(proc.in);\n        close(proc.out);\n        if (use_sideband)\n                finish_async(&muxer);\n-- snap --\n\n2. test helper for proc-receive should consume the input stream before\ndie earlier:\n\n-- snip --\n@@ -79,9 +79,11 @@ static void proc_receive_read_commands(struct\npacket_reader *reader,\n                    *p++ != ' ' ||\n                    parse_oid_hex(p, &new_oid, &p) ||\n                    *p++ != ' ' ||\n-                   die_readline)\n+                   die_readline) {\n+                       while (packet_reader_read(reader) != PACKET_READ_EOF);\n                        die(\"protocol error: expected 'old new ref', got '%s'\",\n-                           reader->line);\n+                               die_readline? \"<call with\n--die-readline>\": reader->line);\n+               }\n                refname = p;\n                FLEX_ALLOC_STR(cmd, ref_name, refname);\n                oidcpy(&cmd->old_oid, &old_oid);\n@@ -136,7 +138,7 @@ int cmd__proc_receive(int argc, const char **argv)\n                usage_msg_opt(\"Too many arguments.\",\nproc_receive_usage, options);\n        packet_reader_init(&reader, 0, NULL, 0,\n                           PACKET_READ_CHOMP_NEWLINE |\n-                          PACKET_READ_DIE_ON_ERR_PACKET);\n+                          PACKET_READ_GENTLE_ON_EOF);\n\n        sigchain_push(SIGPIPE, SIG_IGN);\n        proc_receive_verison(&reader);\n-- snap --\n\nI will send a standalone patch using git-send-email command line later.\n\n--\nJiang Xin\n"},{"id":"409204","messageId":"20201105152329.12736-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"CANYiYbFcXN_ARmZ70GQkrQ=uFaf7YPObYcopEOFseON6TQzNpQ@mail.gmail.com","subject":"[RFC PATCH] t5411: fix broken pipe write error on proc-receive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-11-05T15:23:29Z","receivedAt":"2020-11-05T15:23:37Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nJohannes found a flaky hang in t5411 in the osx-clang job of the CI/PR\nbuilds, and ran into this issue using `--stress` option with the\nfollowing errror messages:\n\n    fatal: unable to write flush packet: Broken pipe\n    send-pack: unexpected disconnect while reading sideband packet\n    fatal: the remote end hung up unexpectedly\n\nIn this test case, the \"proc-receive\" hook sends an error message and\ndies earlier. While \"receive-pack\" on the other side of the pipe\nshould forward the error message of the \"proc-receive\" hook to the\nclient side, but there is no such error message in output. It seems\nthat the expected error message is overridden by the broken pipe error\nmessage. The broken pipe error is because \"receive-pack\" sends other\ncommands to the \"proc-receive\" hook, but the hook dies earlier.\n\nTo fix this issue, these changes are made:\n\n1. In \"receive-pack\", close the input stream for the \"proc-receive\" hook\n   before reading result from \"proc-receive\".\n\n2. The test helper for \"proc-receive\" consumes the input stream before\n   it die earlier.\n\nReported-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c            | 4 +++-\n t/helper/test-proc-receive.c      | 8 +++++---\n t/t5411/test-0013-bad-protocol.sh | 3 +--\n 3 files changed, 9 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex bb9909c52e..6bd402897c 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1172,6 +1172,7 @@ static int run_proc_receive_hook(struct command *commands,\n \tif (version != 1) {\n \t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n \t\t\t    version);\n+\t\tclose(proc.in);\n \t\tcode = -1;\n \t\tgoto cleanup;\n \t}\n@@ -1196,11 +1197,12 @@ static int run_proc_receive_hook(struct command *commands,\n \t\tpacket_flush(proc.in);\n \t}\n \n+\tclose(proc.in);\n+\n \t/* Read result from proc-receive */\n \tcode = read_proc_receive_report(&reader, commands, &errmsg);\n \n cleanup:\n-\tclose(proc.in);\n \tclose(proc.out);\n \tif (use_sideband)\n \t\tfinish_async(&muxer);\ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nindex 42164d9898..132c74db52 100644\n--- a/t/helper/test-proc-receive.c\n+++ b/t/helper/test-proc-receive.c\n@@ -79,9 +79,11 @@ static void proc_receive_read_commands(struct packet_reader *reader,\n \t\t    *p++ != ' ' ||\n \t\t    parse_oid_hex(p, &new_oid, &p) ||\n \t\t    *p++ != ' ' ||\n-\t\t    die_readline)\n+\t\t    die_readline) {\n+\t\t\twhile (packet_reader_read(reader) != PACKET_READ_EOF);\n \t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n-\t\t\t    reader->line);\n+\t\t\t\tdie_readline? \"<call with --die-readline>\": reader->line);\n+\t\t}\n \t\trefname = p;\n \t\tFLEX_ALLOC_STR(cmd, ref_name, refname);\n \t\toidcpy(&cmd->old_oid, &old_oid);\n@@ -136,7 +138,7 @@ int cmd__proc_receive(int argc, const char **argv)\n \t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n \tpacket_reader_init(&reader, 0, NULL, 0,\n \t\t\t   PACKET_READ_CHOMP_NEWLINE |\n-\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\t\t\t   PACKET_READ_GENTLE_ON_EOF);\n \n \tsigchain_push(SIGPIPE, SIG_IGN);\n \tproc_receive_verison(&reader);\ndiff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh\nindex c5fe4cb37b..ee75515430 100644\n--- a/t/t5411/test-0013-bad-protocol.sh\n+++ b/t/t5411/test-0013-bad-protocol.sh\n@@ -91,8 +91,7 @@ test_expect_success \"proc-receive: bad protocol (hook --die-readline, $PROTOCOL)\n \t\tHEAD:refs/for/master/topic \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n-\n-\tgrep \"remote: fatal: protocol error: expected \\\"old new ref\\\", got \\\"<ZERO-OID> <COMMIT-A> refs/for/master/topic\\\"\" actual &&\n+\tgrep \"remote: fatal: protocol error: expected \\\"old new ref\\\", got \\\"<call with --die-readline>\\\"\" actual &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n-- \n2.29.0.dirty\n\n"},{"id":"409206","messageId":"xmqqo8kbk5r5.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"CANYiYbFcXN_ARmZ70GQkrQ=uFaf7YPObYcopEOFseON6TQzNpQ@mail.gmail.com","subject":"Re: [PATCH v19 03/10] receive-pack: add new proc-receive hook","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-11-05T18:39:26Z","receivedAt":"2020-11-05T18:39:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> I think this is the right way to fix this issue, even though I cannot\n> reproduce this issue in my laptop.\n\nPerhaps it depends on the size of the pipe buffer and timing of the\nprocesses getting scheduled?\n\nThis is a tangent but I very much like the way comments on a set of\nchanges below is given interspersed, explaining why the change in\neach hunk is done next to it.\n\n> 1. In the `run_proc_receive_hook()` function of \"receive-pack\", should\n> close the input (proc.in) before reading result from \"proc-receive\":\n>\n> --  snip --\n> @@ -1196,11 +1197,12 @@ static int run_proc_receive_hook(struct\n> command *commands,\n>                 packet_flush(proc.in);\n>         }\n>\n> +       close(proc.in);\n> +\n>         /* Read result from proc-receive */\n>         code = read_proc_receive_report(&reader, commands, &errmsg);\n>\n>  cleanup:\n> -       close(proc.in);\n>         close(proc.out);\n>         if (use_sideband)\n>                 finish_async(&muxer);\n> -- snap --\n>\n> 2. test helper for proc-receive should consume the input stream before\n> die earlier:\n>\n> -- snip --\n> @@ -79,9 +79,11 @@ static void proc_receive_read_commands(struct\n> packet_reader *reader,\n>                     *p++ != ' ' ||\n>                     parse_oid_hex(p, &new_oid, &p) ||\n>                     *p++ != ' ' ||\n> -                   die_readline)\n> +                   die_readline) {\n> +                       while (packet_reader_read(reader) != PACKET_READ_EOF);\n>                         die(\"protocol error: expected 'old new ref', got '%s'\",\n> -                           reader->line);\n> +                               die_readline? \"<call with\n> --die-readline>\": reader->line);\n> +               }\n>                 refname = p;\n>                 FLEX_ALLOC_STR(cmd, ref_name, refname);\n>                 oidcpy(&cmd->old_oid, &old_oid);\n> @@ -136,7 +138,7 @@ int cmd__proc_receive(int argc, const char **argv)\n>                 usage_msg_opt(\"Too many arguments.\",\n> proc_receive_usage, options);\n>         packet_reader_init(&reader, 0, NULL, 0,\n>                            PACKET_READ_CHOMP_NEWLINE |\n> -                          PACKET_READ_DIE_ON_ERR_PACKET);\n> +                          PACKET_READ_GENTLE_ON_EOF);\n>\n>         sigchain_push(SIGPIPE, SIG_IGN);\n>         proc_receive_verison(&reader);\n> -- snap --\n>\n> I will send a standalone patch using git-send-email command line later.\n>\n> --\n> Jiang Xin\n"},{"id":"409210","messageId":"xmqqk0uzk452.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20201105152329.12736-1-worldhello.net@gmail.com","subject":"Re: [RFC PATCH] t5411: fix broken pipe write error on proc-receive","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-11-05T19:14:17Z","receivedAt":"2020-11-05T19:14:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n>\n> Johannes found a flaky hang in t5411 in the osx-clang job of the CI/PR\n> builds, and ran into this issue using `--stress` option with the\n> following errror messages:\n\ns/errror/error/;\n\n>     fatal: unable to write flush packet: Broken pipe\n>     send-pack: unexpected disconnect while reading sideband packet\n>     fatal: the remote end hung up unexpectedly\n>\n> In this test case, the \"proc-receive\" hook sends an error message and\n> dies earlier. While \"receive-pack\" on the other side of the pipe\n\nMicronit.  \"dies first\" may be easier to read.\n\n> should forward the error message of the \"proc-receive\" hook to the\n> client side, but there is no such error message in output. It seems\n> that the expected error message is overridden by the broken pipe error\n> message. The broken pipe error is because \"receive-pack\" sends other\n> commands to the \"proc-receive\" hook, but the hook dies earlier.\n\nThe way the exchange designed to happen in a successfull case is\nthat receive-pack process feeds the commands over the pipe, and\nafter feeding all commands start reading response?  Even if the\nhooks were forbidden from (1) disconnect before reading the commands\nand/or (2) speaking before receive-pack finishes feeding the\ncommands, since they are end-user-supplied random scripts,\nreceive-pack needs to be prepared to deal with misbehaving hooks.\n\n> To fix this issue, these changes are made:\n>\n> 1. In \"receive-pack\", close the input stream for the \"proc-receive\" hook\n>    before reading result from \"proc-receive\".\n\nThis is necessary because...?  Until/unless we close our end, the\nhook would not know we finished talking to them, so it is a good\ndiscipline to close our end of the pipe, but it is unclear to me how\nthis causes the broken pipe failure, i.e. write by receive-pack into\na pipe connected to the hook that has already died.\n\n> 2. The test helper for \"proc-receive\" consumes the input stream before\n>    it die earlier.\n\n\"before it dies.\"\n\nThis is merely a workaround in the hook used for testing.  End-user\nhook that misbehaves can still disconnect without reading anything,\nand receive-pack needs to be prepared for such a case, no?\n\nIs it irrelevant because this is only about forcing a flakey test\nthat fails in two different ways to fail in a predictable way?\n\nThanks.\n\n> Reported-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n> Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n> ---\n>  builtin/receive-pack.c            | 4 +++-\n>  t/helper/test-proc-receive.c      | 8 +++++---\n>  t/t5411/test-0013-bad-protocol.sh | 3 +--\n>  3 files changed, 9 insertions(+), 6 deletions(-)\n>\n> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\n> index bb9909c52e..6bd402897c 100644\n> --- a/builtin/receive-pack.c\n> +++ b/builtin/receive-pack.c\n> @@ -1172,6 +1172,7 @@ static int run_proc_receive_hook(struct command *commands,\n>  \tif (version != 1) {\n>  \t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n>  \t\t\t    version);\n> +\t\tclose(proc.in);\n>  \t\tcode = -1;\n>  \t\tgoto cleanup;\n>  \t}\n> @@ -1196,11 +1197,12 @@ static int run_proc_receive_hook(struct command *commands,\n>  \t\tpacket_flush(proc.in);\n>  \t}\n>  \n> +\tclose(proc.in);\n> +\n>  \t/* Read result from proc-receive */\n>  \tcode = read_proc_receive_report(&reader, commands, &errmsg);\n>  \n>  cleanup:\n> -\tclose(proc.in);\n>  \tclose(proc.out);\n>  \tif (use_sideband)\n>  \t\tfinish_async(&muxer);\n> diff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\n> index 42164d9898..132c74db52 100644\n> --- a/t/helper/test-proc-receive.c\n> +++ b/t/helper/test-proc-receive.c\n> @@ -79,9 +79,11 @@ static void proc_receive_read_commands(struct packet_reader *reader,\n>  \t\t    *p++ != ' ' ||\n>  \t\t    parse_oid_hex(p, &new_oid, &p) ||\n>  \t\t    *p++ != ' ' ||\n> -\t\t    die_readline)\n> +\t\t    die_readline) {\n> +\t\t\twhile (packet_reader_read(reader) != PACKET_READ_EOF);\n\nHave the empty statement on its own line, i.e.\n\n\t\t\twhile (condition)\n\t\t\t\t; /* do nothing */\n\n>  \t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n> -\t\t\t    reader->line);\n> +\t\t\t\tdie_readline? \"<call with --die-readline>\": reader->line);\n\nSP around both sides of \"?\" and \":\", and if that makes the line too\nlong, consider wrapping the line, i.e.\n\n\t\t\t\tdie_readline\n\t\t\t\t? \"<call with --die-readline>\"\n\t\t\t\t: reader->line);\n\n> +\t\t}\n>  \t\trefname = p;\n>  \t\tFLEX_ALLOC_STR(cmd, ref_name, refname);\n>  \t\toidcpy(&cmd->old_oid, &old_oid);\n> @@ -136,7 +138,7 @@ int cmd__proc_receive(int argc, const char **argv)\n>  \t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n>  \tpacket_reader_init(&reader, 0, NULL, 0,\n>  \t\t\t   PACKET_READ_CHOMP_NEWLINE |\n> -\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n> +\t\t\t   PACKET_READ_GENTLE_ON_EOF);\n>  \n>  \tsigchain_push(SIGPIPE, SIG_IGN);\n>  \tproc_receive_verison(&reader);\n> diff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh\n> index c5fe4cb37b..ee75515430 100644\n> --- a/t/t5411/test-0013-bad-protocol.sh\n> +++ b/t/t5411/test-0013-bad-protocol.sh\n> @@ -91,8 +91,7 @@ test_expect_success \"proc-receive: bad protocol (hook --die-readline, $PROTOCOL)\n>  \t\tHEAD:refs/for/master/topic \\\n>  \t\t>out 2>&1 &&\n>  \tmake_user_friendly_and_stable_output <out >actual &&\n> -\n> -\tgrep \"remote: fatal: protocol error: expected \\\"old new ref\\\", got \\\"<ZERO-OID> <COMMIT-A> refs/for/master/topic\\\"\" actual &&\n> +\tgrep \"remote: fatal: protocol error: expected \\\"old new ref\\\", got \\\"<call with --die-readline>\\\"\" actual &&\n>  \n>  \tgit -C \"$upstream\" show-ref >out &&\n>  \tmake_user_friendly_and_stable_output <out >actual &&\n"},{"id":"409315","messageId":"20201107025746.13064-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"xmqqk0uzk452.fsf@gitster.c.googlers.com","subject":"[PATCH] t5411: consistent result for proc-receive broken test","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-11-07T02:57:46Z","receivedAt":"2020-11-07T03:07:18Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nJohannes found a flaky hang in `t5411/test-0013-bad-protocol.sh` in the\nosx-clang job of the CI/PR builds, and ran into an issue when using\nthe `--stress` option with the following error messages:\n\n    fatal: unable to write flush packet: Broken pipe\n    send-pack: unexpected disconnect while reading sideband packet\n    fatal: the remote end hung up unexpectedly\n\nIn this test case, the \"proc-receive\" hook sends an error message and\ndies earlier. While \"receive-pack\" on the other side of the pipe\nshould forward the error message of the \"proc-receive\" hook to the\nclient side, but it fails to do so. Perhaps it depends on the size of\nthe pipe buffer and timing of the processes getting scheduled.\n\nThe way the exchange designed to happen in a successful case is that\n\"receive-pack\" process feeds the commands, and optional push options\nover the pipe, and after feeding all these information, \"receive-pack\"\nstarts reading the response of \"proc-receive\". Let \"receive-pack\" close\nthe input stream to \"proc-receive\" right before reading the response,\nso that \"proc-receive\" may consume all the input from \"receive-pack\"\nbefore sending an error message and closing the pipe.\n\nThe \"proc-receive\" hook may close the pipe at any time. Three options\n(--die-version, --die-readline, and --die-report) can be used for the\ntest helper to simulate different broken cases. In order to keep the\ntest results consistent under stress test, only status reports are\nmatched.\n\nReported-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c            |  4 ++-\n t/helper/test-proc-receive.c      | 20 +++++++++---\n t/t5411/test-0013-bad-protocol.sh | 53 +++++++++++++++++++++++++------\n 3 files changed, 63 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex bb9909c52e..6bd402897c 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1172,6 +1172,7 @@ static int run_proc_receive_hook(struct command *commands,\n \tif (version != 1) {\n \t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n \t\t\t    version);\n+\t\tclose(proc.in);\n \t\tcode = -1;\n \t\tgoto cleanup;\n \t}\n@@ -1196,11 +1197,12 @@ static int run_proc_receive_hook(struct command *commands,\n \t\tpacket_flush(proc.in);\n \t}\n \n+\tclose(proc.in);\n+\n \t/* Read result from proc-receive */\n \tcode = read_proc_receive_report(&reader, commands, &errmsg);\n \n cleanup:\n-\tclose(proc.in);\n \tclose(proc.out);\n \tif (use_sideband)\n \t\tfinish_async(&muxer);\ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nindex 42164d9898..ee5717ba0a 100644\n--- a/t/helper/test-proc-receive.c\n+++ b/t/helper/test-proc-receive.c\n@@ -12,6 +12,7 @@ static const char *proc_receive_usage[] = {\n \n static int die_version;\n static int die_readline;\n+static int die_report;\n static int no_push_options;\n static int use_atomic;\n static int use_push_options;\n@@ -52,8 +53,10 @@ static void proc_receive_verison(struct packet_reader *reader) {\n \t\t}\n \t}\n \n-\tif (server_version != 1 || die_version)\n+\tif (server_version != 1)\n \t\tdie(\"bad protocol version: %d\", server_version);\n+\tif (die_version)\n+\t\tdie(\"die with the --die-version option\");\n \n \tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n \t\t\t version, '\\0',\n@@ -75,13 +78,18 @@ static void proc_receive_read_commands(struct packet_reader *reader,\n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n \t\t\tbreak;\n \n+\t\tif (die_readline)\n+\t\t\tdie(\"die with the --die-readline option\");\n+\n \t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n \t\t    *p++ != ' ' ||\n \t\t    parse_oid_hex(p, &new_oid, &p) ||\n-\t\t    *p++ != ' ' ||\n-\t\t    die_readline)\n+\t\t    *p++ != ' ') {\n+\t\t\twhile (packet_reader_read(reader) != PACKET_READ_EOF)\n+\t\t\t\t; /* do nothing */\n \t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n \t\t\t    reader->line);\n+\t\t}\n \t\trefname = p;\n \t\tFLEX_ALLOC_STR(cmd, ref_name, refname);\n \t\toidcpy(&cmd->old_oid, &old_oid);\n@@ -121,6 +129,8 @@ int cmd__proc_receive(int argc, const char **argv)\n \t\t\t \"die during version negotiation\"),\n \t\tOPT_BOOL(0, \"die-readline\", &die_readline,\n \t\t\t \"die when readline\"),\n+\t\tOPT_BOOL(0, \"die-report\", &die_report,\n+\t\t\t \"die when reporting\"),\n \t\tOPT_STRING_LIST('r', \"return\", &returns, \"old/new/ref/status/msg\",\n \t\t\t\t\"return of results\"),\n \t\tOPT__VERBOSE(&verbose, \"be verbose\"),\n@@ -136,7 +146,7 @@ int cmd__proc_receive(int argc, const char **argv)\n \t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n \tpacket_reader_init(&reader, 0, NULL, 0,\n \t\t\t   PACKET_READ_CHOMP_NEWLINE |\n-\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\t\t\t   PACKET_READ_GENTLE_ON_EOF);\n \n \tsigchain_push(SIGPIPE, SIG_IGN);\n \tproc_receive_verison(&reader);\n@@ -166,6 +176,8 @@ int cmd__proc_receive(int argc, const char **argv)\n \t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n \t}\n \n+\tif (die_report)\n+\t\tdie(\"die with the --die-report option\");\n \tif (returns.nr)\n \t\tfor_each_string_list_item(item, &returns)\n \t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\ndiff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh\nindex c5fe4cb37b..5c5241bc95 100644\n--- a/t/t5411/test-0013-bad-protocol.sh\n+++ b/t/t5411/test-0013-bad-protocol.sh\n@@ -55,19 +55,16 @@ test_expect_success \"proc-receive: bad protocol (hook --die-version, $PROTOCOL)\"\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/master/topic \\\n \t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\n+\tmake_user_friendly_and_stable_output <out |\n+\t\tsed -n \\\n+\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n+\t\t\t-e \"/^ ! / { s/   */ /g; p; }\" \\\n+\t\t\t>actual &&\n \tcat >expect <<-EOF &&\n-\tremote: # pre-receive hook\n-\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n-\tremote: # proc-receive hook\n-\tremote: fatal: bad protocol version: 1\n-\tremote: error: proc-receive version \"0\" is not supported\n \tTo <URL/of/upstream.git>\n \t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n \tEOF\n \ttest_cmp expect actual &&\n-\n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n@@ -90,10 +87,48 @@ test_expect_success \"proc-receive: bad protocol (hook --die-readline, $PROTOCOL)\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/master/topic \\\n \t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out |\n+\t\tsed -n \\\n+\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n+\t\t\t-e \"/^ ! / { s/   */ /g; p; }\" \\\n+\t\t\t>actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n \n-\tgrep \"remote: fatal: protocol error: expected \\\"old new ref\\\", got \\\"<ZERO-OID> <COMMIT-A> refs/for/master/topic\\\"\" actual &&\n+test_expect_success \"setup proc-receive hook (hook --die-report, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-report\n+\tEOF\n+'\n \n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-report, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out |\n+\t\tsed -n \\\n+\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n+\t\t\t-e \"/^ ! / { s/   */ /g; p; }\" \\\n+\t\t\t>actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n-- \n2.29.0.dirty\n\n"},{"id":"409374","messageId":"CANYiYbHQKshFg=1xAv8MFfSjmFfQ0uJRm3mQBnZMsTd1n7R-Ow@mail.gmail.com","threadId":"52936","inReplyTo":"20201107025746.13064-1-worldhello.net@gmail.com","subject":"Re: [PATCH] t5411: consistent result for proc-receive broken test","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-11-09T07:29:17Z","receivedAt":"2020-11-09T07:29:32Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> 于2020年11月7日周六 上午10:57写道：\n> @@ -75,13 +78,18 @@ static void proc_receive_read_commands(struct packet_reader *reader,\n>                 if (packet_reader_read(reader) != PACKET_READ_NORMAL)\n>                         break;\n>\n> +               if (die_readline)\n> +                       die(\"die with the --die-readline option\");\n> +\n>                 if (parse_oid_hex(reader->line, &old_oid, &p) ||\n>                     *p++ != ' ' ||\n>                     parse_oid_hex(p, &new_oid, &p) ||\n> -                   *p++ != ' ' ||\n> -                   die_readline)\n> +                   *p++ != ' ') {\n> +                       while (packet_reader_read(reader) != PACKET_READ_EOF)\n> +                               ; /* do nothing */\n>                         die(\"protocol error: expected 'old new ref', got '%s'\",\n>                             reader->line);\n> +               }\n>                 refname = p;\n>                 FLEX_ALLOC_STR(cmd, ref_name, refname);\n>                 oidcpy(&cmd->old_oid, &old_oid);\n\nStill have problems under the stress test.  Today I figured out how to\nrun stress test by setting proper environment variable\nGIT_TEST_STRESS_LOAD without consuming too many resources and leading\nto random strange errors. E.g.:\n\n    $ GIT_TEST_STRESS_LOAD=11 sh  t5411-proc-receive-hook.sh -v --stress\n\nWill send patch v2 later with the following changes:\n\n-- snip --\ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nindex ee5717ba0a..9f7fbc5b7a 100644\n--- a/t/helper/test-proc-receive.c\n+++ b/t/helper/test-proc-receive.c\n@@ -78,17 +78,18 @@ static void proc_receive_read_commands(struct\npacket_reader *reader,\n                if (packet_reader_read(reader) != PACKET_READ_NORMAL)\n                        break;\n\n-               if (die_readline)\n-                       die(\"die with the --die-readline option\");\n-\n                if (parse_oid_hex(reader->line, &old_oid, &p) ||\n                    *p++ != ' ' ||\n                    parse_oid_hex(p, &new_oid, &p) ||\n-                   *p++ != ' ') {\n+                   *p++ != ' ' ||\n+                   die_readline) {\n+                       char *bad_line = xstrdup(reader->line);\n                        while (packet_reader_read(reader) != PACKET_READ_EOF)\n                                ; /* do nothing */\n+                       if (die_readline)\n+                               die(\"die with the --die-readline option\");\n                        die(\"protocol error: expected 'old new ref', got '%s'\",\n-                           reader->line);\n+                           bad_line);\n                }\n                refname = p;\n                FLEX_ALLOC_STR(cmd, ref_name, refname);\n-- snap --\n"},{"id":"409388","messageId":"20201109105846.64303-1-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"CANYiYbHQKshFg=1xAv8MFfSjmFfQ0uJRm3mQBnZMsTd1n7R-Ow@mail.gmail.com","subject":"[PATCH v2] t5411: consistent result for proc-receive broken test","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-11-09T10:58:46Z","receivedAt":"2020-11-09T10:59:00Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Johannes found a flaky hang in `t5411/test-0013-bad-protocol.sh` in the\nosx-clang job of the CI/PR builds, and ran into an issue when using\nthe `--stress` option with the following error messages:\n\n    fatal: unable to write flush packet: Broken pipe\n    send-pack: unexpected disconnect while reading sideband packet\n    fatal: the remote end hung up unexpectedly\n\nIn this test case, the \"proc-receive\" hook sends an error message and\ndies earlier. While \"receive-pack\" on the other side of the pipe\nshould forward the error message of the \"proc-receive\" hook to the\nclient side, but it fails to do so. Perhaps it depends on the size of\nthe pipe buffer and timing of the processes getting scheduled.\n\nThe way the exchange designed to happen in a successful case is that\n\"receive-pack\" process feeds the commands, and optional push options\nover the pipe, and after feeding all these information, \"receive-pack\"\nstarts reading the response of \"proc-receive\". Let \"receive-pack\" close\nthe input stream to \"proc-receive\" right before reading the response,\nso that \"proc-receive\" may consume all the input from \"receive-pack\"\nbefore sending an error message and closing the pipe.\n\nThe \"proc-receive\" hook may close the pipe at any time. Three options\n(--die-version, --die-readline, and --die-report) can be used for the\ntest helper to simulate different broken cases. In order to keep the\ntest results consistent under stress test, only status reports are\nmatched.\n\nReported-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c                       |  4 +-\n t/helper/test-proc-receive.c                 | 21 ++++-\n t/t5411/test-0013-bad-protocol.sh            | 53 +++++++++--\n t/t5411/test-0014-bad-protocol--porcelain.sh | 96 ++++++++++++++++++++\n 4 files changed, 160 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex bb9909c52e..6bd402897c 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1172,6 +1172,7 @@ static int run_proc_receive_hook(struct command *commands,\n \tif (version != 1) {\n \t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n \t\t\t    version);\n+\t\tclose(proc.in);\n \t\tcode = -1;\n \t\tgoto cleanup;\n \t}\n@@ -1196,11 +1197,12 @@ static int run_proc_receive_hook(struct command *commands,\n \t\tpacket_flush(proc.in);\n \t}\n \n+\tclose(proc.in);\n+\n \t/* Read result from proc-receive */\n \tcode = read_proc_receive_report(&reader, commands, &errmsg);\n \n cleanup:\n-\tclose(proc.in);\n \tclose(proc.out);\n \tif (use_sideband)\n \t\tfinish_async(&muxer);\ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nindex 42164d9898..9f7fbc5b7a 100644\n--- a/t/helper/test-proc-receive.c\n+++ b/t/helper/test-proc-receive.c\n@@ -12,6 +12,7 @@ static const char *proc_receive_usage[] = {\n \n static int die_version;\n static int die_readline;\n+static int die_report;\n static int no_push_options;\n static int use_atomic;\n static int use_push_options;\n@@ -52,8 +53,10 @@ static void proc_receive_verison(struct packet_reader *reader) {\n \t\t}\n \t}\n \n-\tif (server_version != 1 || die_version)\n+\tif (server_version != 1)\n \t\tdie(\"bad protocol version: %d\", server_version);\n+\tif (die_version)\n+\t\tdie(\"die with the --die-version option\");\n \n \tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n \t\t\t version, '\\0',\n@@ -79,9 +82,15 @@ static void proc_receive_read_commands(struct packet_reader *reader,\n \t\t    *p++ != ' ' ||\n \t\t    parse_oid_hex(p, &new_oid, &p) ||\n \t\t    *p++ != ' ' ||\n-\t\t    die_readline)\n+\t\t    die_readline) {\n+\t\t\tchar *bad_line = xstrdup(reader->line);\n+\t\t\twhile (packet_reader_read(reader) != PACKET_READ_EOF)\n+\t\t\t\t; /* do nothing */\n+\t\t\tif (die_readline)\n+\t\t\t\tdie(\"die with the --die-readline option\");\n \t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n-\t\t\t    reader->line);\n+\t\t\t    bad_line);\n+\t\t}\n \t\trefname = p;\n \t\tFLEX_ALLOC_STR(cmd, ref_name, refname);\n \t\toidcpy(&cmd->old_oid, &old_oid);\n@@ -121,6 +130,8 @@ int cmd__proc_receive(int argc, const char **argv)\n \t\t\t \"die during version negotiation\"),\n \t\tOPT_BOOL(0, \"die-readline\", &die_readline,\n \t\t\t \"die when readline\"),\n+\t\tOPT_BOOL(0, \"die-report\", &die_report,\n+\t\t\t \"die when reporting\"),\n \t\tOPT_STRING_LIST('r', \"return\", &returns, \"old/new/ref/status/msg\",\n \t\t\t\t\"return of results\"),\n \t\tOPT__VERBOSE(&verbose, \"be verbose\"),\n@@ -136,7 +147,7 @@ int cmd__proc_receive(int argc, const char **argv)\n \t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n \tpacket_reader_init(&reader, 0, NULL, 0,\n \t\t\t   PACKET_READ_CHOMP_NEWLINE |\n-\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\t\t\t   PACKET_READ_GENTLE_ON_EOF);\n \n \tsigchain_push(SIGPIPE, SIG_IGN);\n \tproc_receive_verison(&reader);\n@@ -166,6 +177,8 @@ int cmd__proc_receive(int argc, const char **argv)\n \t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n \t}\n \n+\tif (die_report)\n+\t\tdie(\"die with the --die-report option\");\n \tif (returns.nr)\n \t\tfor_each_string_list_item(item, &returns)\n \t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\ndiff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh\nindex c5fe4cb37b..5c5241bc95 100644\n--- a/t/t5411/test-0013-bad-protocol.sh\n+++ b/t/t5411/test-0013-bad-protocol.sh\n@@ -55,19 +55,16 @@ test_expect_success \"proc-receive: bad protocol (hook --die-version, $PROTOCOL)\"\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/master/topic \\\n \t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\n+\tmake_user_friendly_and_stable_output <out |\n+\t\tsed -n \\\n+\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n+\t\t\t-e \"/^ ! / { s/   */ /g; p; }\" \\\n+\t\t\t>actual &&\n \tcat >expect <<-EOF &&\n-\tremote: # pre-receive hook\n-\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic\n-\tremote: # proc-receive hook\n-\tremote: fatal: bad protocol version: 1\n-\tremote: error: proc-receive version \"0\" is not supported\n \tTo <URL/of/upstream.git>\n \t ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)\n \tEOF\n \ttest_cmp expect actual &&\n-\n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n@@ -90,10 +87,48 @@ test_expect_success \"proc-receive: bad protocol (hook --die-readline, $PROTOCOL)\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/master/topic \\\n \t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out |\n+\t\tsed -n \\\n+\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n+\t\t\t-e \"/^ ! / { s/   */ /g; p; }\" \\\n+\t\t\t>actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n \n-\tgrep \"remote: fatal: protocol error: expected \\\"old new ref\\\", got \\\"<ZERO-OID> <COMMIT-A> refs/for/master/topic\\\"\" actual &&\n+test_expect_success \"setup proc-receive hook (hook --die-report, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-report\n+\tEOF\n+'\n \n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-report, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out |\n+\t\tsed -n \\\n+\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n+\t\t\t-e \"/^ ! / { s/   */ /g; p; }\" \\\n+\t\t\t>actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)\n+\tEOF\n+\ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\ndiff --git a/t/t5411/test-0014-bad-protocol--porcelain.sh b/t/t5411/test-0014-bad-protocol--porcelain.sh\nindex 53b47b0185..df19777ab0 100644\n--- a/t/t5411/test-0014-bad-protocol--porcelain.sh\n+++ b/t/t5411/test-0014-bad-protocol--porcelain.sh\n@@ -42,6 +42,102 @@ test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL/porc\n \ttest_cmp expect actual\n '\n \n+test_expect_success \"setup proc-receive hook (hook --die-version, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-version\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-version, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out |\n+\t\tsed -n \\\n+\t\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n+\t\t\t>actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-readline, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-readline\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-readline, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out |\n+\t\tsed -n \\\n+\t\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n+\t\t\t>actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-report, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-report\n+\tEOF\n+'\n+\n+# Refs of upstream : master(A)\n+# Refs of workbench: master(A)  tags/v123\n+# git push         :                       refs/for/master/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-report, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/master/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out |\n+\t\tsed -n \\\n+\t\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n+\t\t\t>actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/master/topic    [remote rejected] (proc-receive failed to report status)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/master\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success \"setup proc-receive hook (no report, $PROTOCOL/porcelain)\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n-- \n2.29.0.dirty\n\n"},{"id":"409410","messageId":"xmqqh7pyb61f.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20201109105846.64303-1-zhiyou.jx@alibaba-inc.com","subject":"Re: [PATCH v2] t5411: consistent result for proc-receive broken test","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-11-09T20:59:24Z","receivedAt":"2020-11-09T20:59:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\n> index bb9909c52e..6bd402897c 100644\n> --- a/builtin/receive-pack.c\n> +++ b/builtin/receive-pack.c\n> @@ -1172,6 +1172,7 @@ static int run_proc_receive_hook(struct command *commands,\n>  \tif (version != 1) {\n>  \t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n>  \t\t\t    version);\n> +\t\tclose(proc.in);\n>  \t\tcode = -1;\n>  \t\tgoto cleanup;\n>  \t}\n> @@ -1196,11 +1197,12 @@ static int run_proc_receive_hook(struct command *commands,\n>  \t\tpacket_flush(proc.in);\n>  \t}\n>  \n> +\tclose(proc.in);\n> +\n>  \t/* Read result from proc-receive */\n>  \tcode = read_proc_receive_report(&reader, commands, &errmsg);\n>  \n>  cleanup:\n> -\tclose(proc.in);\n>  \tclose(proc.out);\n>  \tif (use_sideband)\n>  \t\tfinish_async(&muxer);\n\nOK, without us closing our end, the hook cannot even tell that it\nread to the end of our input.\n\n> diff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\n> index 42164d9898..9f7fbc5b7a 100644\n> --- a/t/helper/test-proc-receive.c\n> +++ b/t/helper/test-proc-receive.c\n> @@ -12,6 +12,7 @@ static const char *proc_receive_usage[] = {\n>  \n>  static int die_version;\n>  static int die_readline;\n> +static int die_report;\n>  static int no_push_options;\n>  static int use_atomic;\n>  static int use_push_options;\n> @@ -52,8 +53,10 @@ static void proc_receive_verison(struct packet_reader *reader) {\n>  \t\t}\n>  \t}\n>  \n> -\tif (server_version != 1 || die_version)\n> +\tif (server_version != 1)\n>  \t\tdie(\"bad protocol version: %d\", server_version);\n> +\tif (die_version)\n> +\t\tdie(\"die with the --die-version option\");\n\nIf any of these trigger, wouldn't we end up dying without consuming\nwhat receive-pack said?\n\n>  \n>  \tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n>  \t\t\t version, '\\0',\n> @@ -79,9 +82,15 @@ static void proc_receive_read_commands(struct packet_reader *reader,\n>  \t\t    *p++ != ' ' ||\n>  \t\t    parse_oid_hex(p, &new_oid, &p) ||\n>  \t\t    *p++ != ' ' ||\n> -\t\t    die_readline)\n> +\t\t    die_readline) {\n> +\t\t\tchar *bad_line = xstrdup(reader->line);\n> +\t\t\twhile (packet_reader_read(reader) != PACKET_READ_EOF)\n> +\t\t\t\t; /* do nothing */\n> +\t\t\tif (die_readline)\n> +\t\t\t\tdie(\"die with the --die-readline option\");\n>  \t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n> -\t\t\t    reader->line);\n> +\t\t\t    bad_line);\n> +\t\t}\n\nThis part is different from the previous one in that it slurps all\nthe input before dying evein in die_readline case.\n\n> @@ -166,6 +177,8 @@ int cmd__proc_receive(int argc, const char **argv)\n>  \t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n>  \t}\n>  \n> +\tif (die_report)\n> +\t\tdie(\"die with the --die-report option\");\n\nAnd at this point we have already read everything the other end\nsaid (if so, there is no need for the artificial \"read everything\nbefore we die\")?\n\n> diff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh\n> index c5fe4cb37b..5c5241bc95 100644\n> --- a/t/t5411/test-0013-bad-protocol.sh\n> +++ b/t/t5411/test-0013-bad-protocol.sh\n> @@ -55,19 +55,16 @@ test_expect_success \"proc-receive: bad protocol (hook --die-version, $PROTOCOL)\"\n>  \ttest_must_fail git -C workbench push origin \\\n>  \t\tHEAD:refs/for/master/topic \\\n>  \t\t>out 2>&1 &&\n\nAre these expected to conflict with Dscho's changes to move 'master'\naround?\n\n> -\tmake_user_friendly_and_stable_output <out >actual &&\n> -\n> +\tmake_user_friendly_and_stable_output <out |\n> +\t\tsed -n \\\n> +\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n> +\t\t\t-e \"/^ ! / { s/   */ /g; p; }\" \\\n> +\t\t\t>actual &&\n\nIt's the same thing but I somehow find \"s/  */ /g\" easier to read.\nThe comparison is between \"there may be two things or more---squish\nthem down to one\" and \"After one thing, there may be any number of\nthings---remove all the extra ones\".\n\nMakes me wonder if make_user_friendly should optionally have an\noption to do something like this for us.  I doubt it that it is\nworth to do something like the attached patch.\n\n\n t/t5411/common-functions.sh | 7 +++++--\n 1 file changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git c/t/t5411/common-functions.sh w/t/t5411/common-functions.sh\nindex 6580bebd8e..6919639c60 100644\n--- c/t/t5411/common-functions.sh\n+++ w/t/t5411/common-functions.sh\n@@ -40,7 +40,9 @@ create_commits_in () {\n # `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n # on plumbing commands.\n make_user_friendly_and_stable_output () {\n-\tsed \\\n+\tlocal en=\n+\tcase \"$#\" in 0) ;; *) en=-n ;; esac\n+\tsed $en \\\n \t\t-e \"s/  *\\$//\" \\\n \t\t-e \"s/   */ /g\" \\\n \t\t-e \"s/'/\\\"/g\" \\\n@@ -52,5 +54,6 @@ make_user_friendly_and_stable_output () {\n \t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n \t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\" \\\n \t\t-e \"s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#\" \\\n-\t\t-e \"/^error: / d\"\n+\t\t-e \"/^error: / d\" \\\n+\t\t${1+\"$@\"}\n }\n"},{"id":"409434","messageId":"20201109231246.GA677345@coredump.intra.peff.net","threadId":"52936","inReplyTo":"xmqqh7pyb61f.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v2] t5411: consistent result for proc-receive broken test","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-09T23:12:46Z","receivedAt":"2020-11-09T23:12:49Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Nov 09, 2020 at 12:59:24PM -0800, Junio C Hamano wrote:\n\n> > @@ -1196,11 +1197,12 @@ static int run_proc_receive_hook(struct command *commands,\n> >  \t\tpacket_flush(proc.in);\n> >  \t}\n> >  \n> > +\tclose(proc.in);\n> > +\n> >  \t/* Read result from proc-receive */\n> >  \tcode = read_proc_receive_report(&reader, commands, &errmsg);\n> >  \n> >  cleanup:\n> > -\tclose(proc.in);\n> >  \tclose(proc.out);\n> >  \tif (use_sideband)\n> >  \t\tfinish_async(&muxer);\n> \n> OK, without us closing our end, the hook cannot even tell that it\n> read to the end of our input.\n\nThat doesn't seem right. It should be expecting our flush packet,\nshouldn't it? And if it sees an EOF before the flush packet, that would\nbe an error from the hook's perspective.\n\nThis part of the patch seems like a red herring to me.\n\n> > @@ -52,8 +53,10 @@ static void proc_receive_verison(struct packet_reader *reader) {\n> >  \t\t}\n> >  \t}\n> >  \n> > -\tif (server_version != 1 || die_version)\n> > +\tif (server_version != 1)\n> >  \t\tdie(\"bad protocol version: %d\", server_version);\n> > +\tif (die_version)\n> > +\t\tdie(\"die with the --die-version option\");\n> \n> If any of these trigger, wouldn't we end up dying without consuming\n> what receive-pack said?\n\nYeah, I think they would have the same race that the commit message\ndescribes (proc-receive hook writes to stderr and dies, receive-pack\ngets an error writing to now-closed hook pipe and never relays the\nstderr).\n\nBut it seems like fixing this in the hook is the wrong place. The hook\nhas failed and has nothing else to say. Adding a pump-the-stdin-to-eof\nloop to every die() is a lot of effort. Not to mention that the hook\ncould fail for reasons outside its usual flow control (e.g., segfault,\noom, etc, and receive-pack should be able to handle that gracefully,\neven if the hook doesn't appear to behave.\n\nI.e., I think the bug is in receive-pack's run_proc_receive_hook(). It\ncleverly ignores SIGPIPE exactly to avoid dying during the write phase,\nbut then it proceeds to call packet_write_fmt(), etc, that will die on\nany error (going to extra effort to emulate sigpipe, no less!). So we\ndie and take our sideband muxer with us.\n\nSo instead of this hunk:\n\n> > @@ -79,9 +82,15 @@ static void proc_receive_read_commands(struct packet_reader *reader,\n> >  \t\t    *p++ != ' ' ||\n> >  \t\t    parse_oid_hex(p, &new_oid, &p) ||\n> >  \t\t    *p++ != ' ' ||\n> > -\t\t    die_readline)\n> > +\t\t    die_readline) {\n> > +\t\t\tchar *bad_line = xstrdup(reader->line);\n> > +\t\t\twhile (packet_reader_read(reader) != PACKET_READ_EOF)\n> > +\t\t\t\t; /* do nothing */\n> > +\t\t\tif (die_readline)\n> > +\t\t\t\tdie(\"die with the --die-readline option\");\n> >  \t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n> > -\t\t\t    reader->line);\n> > +\t\t\t    bad_line);\n> > +\t\t}\n> \n> This part is different from the previous one in that it slurps all\n> the input before dying evein in die_readline case.\n\nI think the patch really ought to be in receive-pack, converting\npacket_write_fmt() and packet_flush() into their \"gently\" forms.\n\nAnd when we see a write error, close our pipe to the hook, set errmsg to\n\"hook failed to run\" or similar, and then jump to the \"cleanup\" label,\nwhere we'll wait on our sideband muxer to finish (which in turn will\nwait pump any remaining data out of the hook's stderr).\n\nOptionally we can pump the hook stdout to see if it gave us a better\nmessage, but I think if write() failed, then all bets are off. The hook\nbroke protocol; a well-behaved hook that wanted to pass along a specific\nper-ref message to the user would actually read all the input and then\nreport on each ref).\n\n-Peff\n"},{"id":"409436","messageId":"xmqqk0uu9kuf.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20201109231246.GA677345@coredump.intra.peff.net","subject":"Re: [PATCH v2] t5411: consistent result for proc-receive broken test","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-11-09T23:22:32Z","receivedAt":"2020-11-09T23:22:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> I.e., I think the bug is in receive-pack's run_proc_receive_hook().\n\nI thought that this was merely to work it around and give the\nparticular test a reliable failure code, though.  Either way,\nreceive-pack will get a failure when the hook fails (either the\nfailure message from the hook, or unexpected EOF), so no matter what\nthis patch does around this area, it would not affect interactions\nwith real-life hooks, I would think.\n\n> It\n> cleverly ignores SIGPIPE exactly to avoid dying during the write phase,\n> but then it proceeds to call packet_write_fmt(), etc, that will die on\n> any error (going to extra effort to emulate sigpipe, no less!). So we\n> die and take our sideband muxer with us.\n>\n> So instead of this hunk:\n>\n>> > @@ -79,9 +82,15 @@ static void proc_receive_read_commands(struct packet_reader *reader,\n>> >  \t\t    *p++ != ' ' ||\n>> >  \t\t    parse_oid_hex(p, &new_oid, &p) ||\n>> >  \t\t    *p++ != ' ' ||\n>> > -\t\t    die_readline)\n>> > +\t\t    die_readline) {\n>> > +\t\t\tchar *bad_line = xstrdup(reader->line);\n>> > +\t\t\twhile (packet_reader_read(reader) != PACKET_READ_EOF)\n>> > +\t\t\t\t; /* do nothing */\n>> > +\t\t\tif (die_readline)\n>> > +\t\t\t\tdie(\"die with the --die-readline option\");\n>> >  \t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n>> > -\t\t\t    reader->line);\n>> > +\t\t\t    bad_line);\n>> > +\t\t}\n>> \n>> This part is different from the previous one in that it slurps all\n>> the input before dying evein in die_readline case.\n>\n> I think the patch really ought to be in receive-pack, converting\n> packet_write_fmt() and packet_flush() into their \"gently\" forms.\n\nThat would be a real fix, not \"work around flakey tests\".  Yes, the\nhook driver roughly mimics run_and_feed_hook() and I suspect it\nstarted from copy-and-paste from that function, so we might need to\nreview that original copy, too.\n\n> And when we see a write error, close our pipe to the hook, set errmsg to\n> \"hook failed to run\" or similar, and then jump to the \"cleanup\" label,\n> where we'll wait on our sideband muxer to finish (which in turn will\n> wait pump any remaining data out of the hook's stderr).\n>\n> Optionally we can pump the hook stdout to see if it gave us a better\n> message, but I think if write() failed, then all bets are off. The hook\n> broke protocol; a well-behaved hook that wanted to pass along a specific\n> per-ref message to the user would actually read all the input and then\n> report on each ref).\n\nYup, sounds good.\n"},{"id":"409439","messageId":"20201110000322.GA687107@coredump.intra.peff.net","threadId":"52936","inReplyTo":"xmqqk0uu9kuf.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v2] t5411: consistent result for proc-receive broken test","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-10T00:03:22Z","receivedAt":"2020-11-10T00:03:24Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Nov 09, 2020 at 03:22:32PM -0800, Junio C Hamano wrote:\n\n> Jeff King <peff@peff.net> writes:\n> \n> > I.e., I think the bug is in receive-pack's run_proc_receive_hook().\n> \n> I thought that this was merely to work it around and give the\n> particular test a reliable failure code, though.  Either way,\n> receive-pack will get a failure when the hook fails (either the\n> failure message from the hook, or unexpected EOF), so no matter what\n> this patch does around this area, it would not affect interactions\n> with real-life hooks, I would think.\n\nYes. Either way receive-pack will fail and won't update any refs, which\nis good. The question is whether or not it gives a useful message back\nto the client or not. So the tests are exposing an interesting\nuser-visible behavior, albeit one which we wouldn't expect to come up\nmuch in practice.\n\n> > I think the patch really ought to be in receive-pack, converting\n> > packet_write_fmt() and packet_flush() into their \"gently\" forms.\n> \n> That would be a real fix, not \"work around flakey tests\".  Yes, the\n> hook driver roughly mimics run_and_feed_hook() and I suspect it\n> started from copy-and-paste from that function, so we might need to\n> review that original copy, too.\n\nGood idea. I think it is fine, though. It does all of its writing\nthrough write_in_full(), and breaks out of the write loop on error.\nWhereas run_proc_receive_hook() switched to using the packet_*\nfunctions, which by default die on write errors. So I think that's the\nroot of the problem.\n\nI found it a little curious that run_and_feed_hook() did not complain on\nwrite error, but I think it is explicitly allowing a hook to \"exit 0\"\nimmediately without even reading the input. As long as the hook exits\nnon-zero, we'd still consider that an error (and because we handled the\nwrite error gracefully, we'd do so reliably).  It would be weird if we\nsaw some _other_ error (EIO or something) while writing, though. I\nsuspect that's all but impossible for a pipe write like this, but it\nwouldn't be wrong to tighten it up, I suppose.\n\nAnd back to run_proc_receive_hook(), I think it _should_ complain about\na write error, rather than allowing the hook to quietly say \"exit 0\".\nThe protocol for the proc-receive hook is more complicated, and we are\nlooking for an exchange of pkt-lines, and not just \"we dumped some data\nand it gave us an exit code\". So I think it failing to read should\nprobably be considered a break of that pkt-line protocol.\n\n-Peff\n"},{"id":"409474","messageId":"CANYiYbEZu9Oq-X=o9aVzNz7BU=8HevnPCuGk=8VQSsz-o2BO6Q@mail.gmail.com","threadId":"52936","inReplyTo":"xmqqh7pyb61f.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v2] t5411: consistent result for proc-receive broken test","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-11-10T11:44:36Z","receivedAt":"2020-11-10T11:44:51Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2020年11月10日周二 上午4:59写道：\n>\n> > @@ -166,6 +177,8 @@ int cmd__proc_receive(int argc, const char **argv)\n> >                               fprintf(stderr, \"proc-receive> %s\\n\", item->string);\n> >       }\n> >\n> > +     if (die_report)\n> > +             die(\"die with the --die-report option\");\n>\n> And at this point we have already read everything the other end\n> said (if so, there is no need for the artificial \"read everything\n> before we die\")?\n\nIn patch v3, will use \"gently\" forms of packet_write_fmt() and\npacket_flush(), and it is no necessary to read everything before die.\nAnd will add more \"--die-*\" option in test helper.\n\n> > diff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh\n> > index c5fe4cb37b..5c5241bc95 100644\n> > --- a/t/t5411/test-0013-bad-protocol.sh\n> > +++ b/t/t5411/test-0013-bad-protocol.sh\n> > @@ -55,19 +55,16 @@ test_expect_success \"proc-receive: bad protocol (hook --die-version, $PROTOCOL)\"\n> >       test_must_fail git -C workbench push origin \\\n> >               HEAD:refs/for/master/topic \\\n> >               >out 2>&1 &&\n>\n> Are these expected to conflict with Dscho's changes to move 'master'\n> around?\n\nSee topic \"gitster/js/default-branch-name-adjust-t5411\" has been merge\nto master branch already. Will rebase on it.\n\n> > -     make_user_friendly_and_stable_output <out >actual &&\n> > -\n> > +     make_user_friendly_and_stable_output <out |\n> > +             sed -n \\\n> > +                     -e \"/^To / { s/   */ /g; p; }\" \\\n> > +                     -e \"/^ ! / { s/   */ /g; p; }\" \\\n> > +                     >actual &&\n>\n> It's the same thing but I somehow find \"s/  */ /g\" easier to read.\n> The comparison is between \"there may be two things or more---squish\n> them down to one\" and \"After one thing, there may be any number of\n> things---remove all the extra ones\".\n>\n> Makes me wonder if make_user_friendly should optionally have an\n> option to do something like this for us.  I doubt it that it is\n> worth to do something like the attached patch.\n>\n>\n>  t/t5411/common-functions.sh | 7 +++++--\n>  1 file changed, 5 insertions(+), 2 deletions(-)\n>\n> diff --git c/t/t5411/common-functions.sh w/t/t5411/common-functions.sh\n> index 6580bebd8e..6919639c60 100644\n> --- c/t/t5411/common-functions.sh\n> +++ w/t/t5411/common-functions.sh\n> @@ -40,7 +40,9 @@ create_commits_in () {\n>  # `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations\n>  # on plumbing commands.\n>  make_user_friendly_and_stable_output () {\n> -       sed \\\n> +       local en=\n> +       case \"$#\" in 0) ;; *) en=-n ;; esac\n> +       sed $en \\\n>                 -e \"s/  *\\$//\" \\\n>                 -e \"s/   */ /g\" \\\n>                 -e \"s/'/\\\"/g\" \\\n> @@ -52,5 +54,6 @@ make_user_friendly_and_stable_output () {\n>                 -e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n>                 -e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\" \\\n>                 -e \"s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#\" \\\n> -               -e \"/^error: / d\"\n> +               -e \"/^error: / d\" \\\n> +               ${1+\"$@\"}\n>  }\n\nIf we call make_user_friendly_and_stable_output like this:\n\n    make_user_friendly_and_stable_output \\\n         -e \"/^To / { p; n; p; n; p; }\n\nText lines next to \"^To \" will not be formatted by the built-in sed's\narguments. So will write like this:\n\n-- snip --\ndiff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\nindex 521a347710..b7cca2d8fb 100644\n--- a/t/t5411/common-functions.sh\n+++ b/t/t5411/common-functions.sh\n@@ -42,7 +42,7 @@ create_commits_in () {\n make_user_friendly_and_stable_output () {\n        sed \\\n                -e \"s/  *\\$//\" \\\n-               -e \"s/   */ /g\" \\\n+               -e \"s/  */ /g\" \\\n                -e \"s/'/\\\"/g\" \\\n                -e \"s/  /    /g\" \\\n                -e \"s/$A/<COMMIT-A>/g\" \\\n@@ -52,5 +52,11 @@ make_user_friendly_and_stable_output () {\n                -e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n                -e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\" \\\n                -e \"s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#\" \\\n-               -e \"/^error: / d\"\n+               -e \"/^error: / d\" | \\\n+       if test $# -eq 0\n+       then\n+               cat\n+       else\n+               sed ${1+\"$@\"}\n+       fi\n }\n-- snap --\n"},{"id":"409475","messageId":"CANYiYbH-x6khgTkkFV29+7AjghOZmG69_6-sQcm2489WMHOWAA@mail.gmail.com","threadId":"52936","inReplyTo":"20201109231246.GA677345@coredump.intra.peff.net","subject":"Re: [PATCH v2] t5411: consistent result for proc-receive broken test","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-11-10T11:49:53Z","receivedAt":"2020-11-10T11:50:06Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Jeff King <peff@peff.net> 于2020年11月10日周二 上午7:12写道：\n> > > @@ -52,8 +53,10 @@ static void proc_receive_verison(struct packet_reader *reader) {\n> > >             }\n> > >     }\n> > >\n> > > -   if (server_version != 1 || die_version)\n> > > +   if (server_version != 1)\n> > >             die(\"bad protocol version: %d\", server_version);\n> > > +   if (die_version)\n> > > +           die(\"die with the --die-version option\");\n> >\n> > If any of these trigger, wouldn't we end up dying without consuming\n> > what receive-pack said?\n>\n> Yeah, I think they would have the same race that the commit message\n> describes (proc-receive hook writes to stderr and dies, receive-pack\n> gets an error writing to now-closed hook pipe and never relays the\n> stderr).\n>\n> But it seems like fixing this in the hook is the wrong place. The hook\n> has failed and has nothing else to say. Adding a pump-the-stdin-to-eof\n> loop to every die() is a lot of effort. Not to mention that the hook\n> could fail for reasons outside its usual flow control (e.g., segfault,\n> oom, etc, and receive-pack should be able to handle that gracefully,\n> even if the hook doesn't appear to behave.\n>\n> I.e., I think the bug is in receive-pack's run_proc_receive_hook(). It\n> cleverly ignores SIGPIPE exactly to avoid dying during the write phase,\n> but then it proceeds to call packet_write_fmt(), etc, that will die on\n> any error (going to extra effort to emulate sigpipe, no less!). So we\n> die and take our sideband muxer with us.\n>\n> So instead of this hunk:\n>\n> > > @@ -79,9 +82,15 @@ static void proc_receive_read_commands(struct packet_reader *reader,\n> > >                 *p++ != ' ' ||\n> > >                 parse_oid_hex(p, &new_oid, &p) ||\n> > >                 *p++ != ' ' ||\n> > > -               die_readline)\n> > > +               die_readline) {\n> > > +                   char *bad_line = xstrdup(reader->line);\n> > > +                   while (packet_reader_read(reader) != PACKET_READ_EOF)\n> > > +                           ; /* do nothing */\n> > > +                   if (die_readline)\n> > > +                           die(\"die with the --die-readline option\");\n> > >                     die(\"protocol error: expected 'old new ref', got '%s'\",\n> > > -                       reader->line);\n> > > +                       bad_line);\n> > > +           }\n> >\n> > This part is different from the previous one in that it slurps all\n> > the input before dying evein in die_readline case.\n>\n> I think the patch really ought to be in receive-pack, converting\n> packet_write_fmt() and packet_flush() into their \"gently\" forms.\n\nThanks Peff for pointing the root cause. Will use the \"gently\" forms\nof packet_write_fmt() and packet_flush() in patch v3.\n\n--\nJiang Xin\n"},{"id":"409476","messageId":"20201110120135.42025-1-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"CANYiYbH-x6khgTkkFV29+7AjghOZmG69_6-sQcm2489WMHOWAA@mail.gmail.com","subject":"[PATCH v3 1/2] t5411: refactor make_user_friendly_and_stable_output","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-11-10T12:01:34Z","receivedAt":"2020-11-10T12:01:48Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Common function `make_user_friendly_and_stable_output()` can take\nadditional arguments to format messages for special cases.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411/common-functions.sh                       | 10 ++++++++--\n t/t5411/test-0000-standard-git-push.sh            |  9 ++++-----\n t/t5411/test-0001-standard-git-push--porcelain.sh | 15 +++++++--------\n 3 files changed, 19 insertions(+), 15 deletions(-)\n\ndiff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\nindex 521a347710..b7cca2d8fb 100644\n--- a/t/t5411/common-functions.sh\n+++ b/t/t5411/common-functions.sh\n@@ -42,7 +42,7 @@ create_commits_in () {\n make_user_friendly_and_stable_output () {\n \tsed \\\n \t\t-e \"s/  *\\$//\" \\\n-\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/  */ /g\" \\\n \t\t-e \"s/'/\\\"/g\" \\\n \t\t-e \"s/\t/    /g\" \\\n \t\t-e \"s/$A/<COMMIT-A>/g\" \\\n@@ -52,5 +52,11 @@ make_user_friendly_and_stable_output () {\n \t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n \t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\" \\\n \t\t-e \"s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#\" \\\n-\t\t-e \"/^error: / d\"\n+\t\t-e \"/^error: / d\" | \\\n+\tif test $# -eq 0\n+\tthen\n+\t\tcat\n+\telse\n+\t\tsed ${1+\"$@\"}\n+\tfi\n }\ndiff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh\nindex 2b04b49367..b3af3f59b0 100644\n--- a/t/t5411/test-0000-standard-git-push.sh\n+++ b/t/t5411/test-0000-standard-git-push.sh\n@@ -36,11 +36,10 @@ test_expect_success \"git-push --atomic ($PROTOCOL)\" '\n \t\tmain \\\n \t\t$B:refs/heads/next \\\n \t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out |\n-\t\tsed -n \\\n-\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n-\t\t\t-e \"/^ ! / { s/   */ /g; p; }\" \\\n-\t\t\t>actual &&\n+\tmake_user_friendly_and_stable_output -n \\\n+\t\t-e \"/^To / { p; }\" \\\n+\t\t-e \"/^ ! / { p; }\" \\\n+\t\t<out >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t ! [rejected] main -> main (non-fast-forward)\ndiff --git a/t/t5411/test-0001-standard-git-push--porcelain.sh b/t/t5411/test-0001-standard-git-push--porcelain.sh\nindex 747307f8da..16ff2d5666 100644\n--- a/t/t5411/test-0001-standard-git-push--porcelain.sh\n+++ b/t/t5411/test-0001-standard-git-push--porcelain.sh\n@@ -37,16 +37,15 @@ test_expect_success \"git-push --atomic ($PROTOCOL/porcelain)\" '\n \t\tmain \\\n \t\t$B:refs/heads/next \\\n \t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out |\n-\t\tsed -n \\\n-\t\t\t-e \"s/^# GETTEXT POISON #//\" \\\n-\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n-\t\t\t-e \"/^! / { s/   */ /g; p; }\" \\\n-\t\t\t>actual &&\n+\tmake_user_friendly_and_stable_output -n \\\n+\t\t-e \"s/^# GETTEXT POISON #//\" \\\n+\t\t-e \"/^To / { p; }\" \\\n+\t\t-e \"/^! / { p; }\" \\\n+\t\t<out >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n-\t! refs/heads/main:refs/heads/main [rejected] (non-fast-forward)\n-\t! <COMMIT-B>:refs/heads/next [rejected] (atomic push failed)\n+\t!    refs/heads/main:refs/heads/main    [rejected] (non-fast-forward)\n+\t!    <COMMIT-B>:refs/heads/next    [rejected] (atomic push failed)\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n-- \n2.29.0.dirty\n\n"},{"id":"409477","messageId":"20201110120135.42025-2-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"CANYiYbH-x6khgTkkFV29+7AjghOZmG69_6-sQcm2489WMHOWAA@mail.gmail.com","subject":"[PATCH v3 2/2] receive-pack: gently write messages to proc-receive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-11-10T12:01:35Z","receivedAt":"2020-11-10T12:01:50Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Johannes found a flaky hang in `t5411/test-0013-bad-protocol.sh` in the\nosx-clang job of the CI/PR builds, and ran into an issue when using\nthe `--stress` option with the following error messages:\n\n    fatal: unable to write flush packet: Broken pipe\n    send-pack: unexpected disconnect while reading sideband packet\n    fatal: the remote end hung up unexpectedly\n\nIn this test case, the \"proc-receive\" hook sends an error message and\ndies earlier. While \"receive-pack\" on the other side of the pipe\nshould forward the error message of the \"proc-receive\" hook to the\nclient side, but it fails to do so. This is because \"receive-pack\"\nuses `packet_write_fmt()` and `packet_flush()` to write pkt-line\nmessage to \"proc-receive\" hook, and these functions die immediately\nwhen pipe is broken. Using \"gently\" forms for these functions will get\nmore predicable output.\n\nAdd more \"--die-*\" options to test helper to test different stages of\nthe protocol between \"receive-pack\" and \"proc-receive\" hook.\n\nReported-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nSuggested-by: Jeff King <peff@peff.net>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c                       |  62 +++++--\n t/helper/test-proc-receive.c                 |  42 +++--\n t/t5411/test-0013-bad-protocol.sh            | 141 +++++++++++++--\n t/t5411/test-0014-bad-protocol--porcelain.sh | 176 ++++++++++++++++++-\n 4 files changed, 380 insertions(+), 41 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex bb9909c52e..697a4e8802 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -974,9 +974,10 @@ static int read_proc_receive_report(struct packet_reader *reader,\n \tstruct command *cmd;\n \tstruct command *hint = NULL;\n \tstruct ref_push_report *report = NULL;\n-\tint new_report = 0;\n \tint code = 0;\n+\tint new_report = 0;\n \tint once = 0;\n+\tint response = 0;\n \n \tfor (;;) {\n \t\tstruct object_id old_oid, new_oid;\n@@ -984,8 +985,14 @@ static int read_proc_receive_report(struct packet_reader *reader,\n \t\tconst char *refname;\n \t\tchar *p;\n \n-\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n+\t\t\tif (!response) {\n+\t\t\t\tstrbuf_addstr(errmsg, \"no response from proc-receive hook\");\n+\t\t\t\treturn -1;\n+\t\t\t}\n \t\t\tbreak;\n+\t\t}\n+\t\tresponse++;\n \n \t\thead = reader->line;\n \t\tp = strchr(head, ' ');\n@@ -1100,7 +1107,7 @@ static int run_proc_receive_hook(struct command *commands,\n \tstruct strbuf cap = STRBUF_INIT;\n \tstruct strbuf errmsg = STRBUF_INIT;\n \tint hook_use_push_options = 0;\n-\tint version = 0;\n+\tint version = -1;\n \tint code;\n \n \targv[0] = find_hook(\"proc-receive\");\n@@ -1145,12 +1152,17 @@ static int run_proc_receive_hook(struct command *commands,\n \tif (use_push_options)\n \t\tstrbuf_addstr(&cap, \" push-options\");\n \tif (cap.len) {\n-\t\tpacket_write_fmt(proc.in, \"version=1%c%s\\n\", '\\0', cap.buf + 1);\n+\t\tcode = packet_write_fmt_gently(proc.in, \"version=1%c%s\\n\", '\\0', cap.buf + 1);\n \t\tstrbuf_release(&cap);\n \t} else {\n-\t\tpacket_write_fmt(proc.in, \"version=1\\n\");\n+\t\tcode = packet_write_fmt_gently(proc.in, \"version=1\\n\");\n+\t}\n+\tif (!code)\n+\t\tcode = packet_flush_gently(proc.in);\n+\tif (code) {\n+\t\tstrbuf_addstr(&errmsg, \"fail to negotiate version with proc-receive hook\");\n+\t\tgoto cleanup;\n \t}\n-\tpacket_flush(proc.in);\n \n \tfor (;;) {\n \t\tint linelen;\n@@ -1169,7 +1181,11 @@ static int run_proc_receive_hook(struct command *commands,\n \t\t}\n \t}\n \n-\tif (version != 1) {\n+\tif (version == -1) {\n+\t\tstrbuf_addstr(&errmsg, \"fail to negotiate version with proc-receive hook\");\n+\t\tcode = -1;\n+\t\tgoto cleanup;\n+\t} else if (version != 1) {\n \t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n \t\t\t    version);\n \t\tcode = -1;\n@@ -1180,20 +1196,36 @@ static int run_proc_receive_hook(struct command *commands,\n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n \t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)\n \t\t\tcontinue;\n-\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n-\t\t\t\t oid_to_hex(&cmd->old_oid),\n-\t\t\t\t oid_to_hex(&cmd->new_oid),\n-\t\t\t\t cmd->ref_name);\n+\t\tcode = packet_write_fmt_gently(proc.in, \"%s %s %s\",\n+\t\t\t\t\t       oid_to_hex(&cmd->old_oid),\n+\t\t\t\t\t       oid_to_hex(&cmd->new_oid),\n+\t\t\t\t\t       cmd->ref_name);\n+\t\tif (code)\n+\t\t\tbreak;\n+\t}\n+\tif (!code)\n+\t\tcode = packet_flush_gently(proc.in);\n+\tif (code) {\n+\t\tstrbuf_addstr(&errmsg, \"fail to write commands to proc-receive hook\");\n+\t\tgoto cleanup;\n \t}\n-\tpacket_flush(proc.in);\n \n \t/* Send push options */\n \tif (hook_use_push_options) {\n \t\tstruct string_list_item *item;\n \n-\t\tfor_each_string_list_item(item, push_options)\n-\t\t\tpacket_write_fmt(proc.in, \"%s\", item->string);\n-\t\tpacket_flush(proc.in);\n+\t\tfor_each_string_list_item(item, push_options) {\n+\t\t\tcode = packet_write_fmt_gently(proc.in, \"%s\", item->string);\n+\t\t\tif (code)\n+\t\t\t\tbreak;\n+\t\t}\n+\t\tif (!code)\n+\t\t\tcode = packet_flush_gently(proc.in);\n+\t\tif (code) {\n+\t\t\tstrbuf_addstr(&errmsg,\n+\t\t\t\t      \"fail to write push-options to proc-receive hook\");\n+\t\t\tgoto cleanup;\n+\t\t}\n \t}\n \n \t/* Read result from proc-receive */\ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nindex 42164d9898..6652cedcee 100644\n--- a/t/helper/test-proc-receive.c\n+++ b/t/helper/test-proc-receive.c\n@@ -10,8 +10,11 @@ static const char *proc_receive_usage[] = {\n \tNULL\n };\n \n-static int die_version;\n-static int die_readline;\n+static int die_read_version;\n+static int die_write_version;\n+static int die_read_commands;\n+static int die_read_push_options;\n+static int die_write_report;\n static int no_push_options;\n static int use_atomic;\n static int use_push_options;\n@@ -33,6 +36,9 @@ struct command {\n static void proc_receive_verison(struct packet_reader *reader) {\n \tint server_version = 0;\n \n+\tif (die_read_version)\n+\t\tdie(\"die with the --die-read-version option\");\n+\n \tfor (;;) {\n \t\tint linelen;\n \n@@ -52,9 +58,12 @@ static void proc_receive_verison(struct packet_reader *reader) {\n \t\t}\n \t}\n \n-\tif (server_version != 1 || die_version)\n+\tif (server_version != 1)\n \t\tdie(\"bad protocol version: %d\", server_version);\n \n+\tif (die_write_version)\n+\t\tdie(\"die with the --die-write-version option\");\n+\n \tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n \t\t\t version, '\\0',\n \t\t\t use_push_options && !no_push_options ? \"push-options\": \"\");\n@@ -75,11 +84,13 @@ static void proc_receive_read_commands(struct packet_reader *reader,\n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n \t\t\tbreak;\n \n+\t\tif (die_read_commands)\n+\t\t\tdie(\"die with the --die-read-commands option\");\n+\n \t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n \t\t    *p++ != ' ' ||\n \t\t    parse_oid_hex(p, &new_oid, &p) ||\n-\t\t    *p++ != ' ' ||\n-\t\t    die_readline)\n+\t\t    *p++ != ' ')\n \t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n \t\t\t    reader->line);\n \t\trefname = p;\n@@ -99,6 +110,9 @@ static void proc_receive_read_push_options(struct packet_reader *reader,\n \tif (no_push_options || !use_push_options)\n \t       return;\n \n+\tif (die_read_push_options)\n+\t\tdie(\"die with the --die-read-push-options option\");\n+\n \twhile (1) {\n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n \t\t\tbreak;\n@@ -117,10 +131,16 @@ int cmd__proc_receive(int argc, const char **argv)\n \tstruct option options[] = {\n \t\tOPT_BOOL(0, \"no-push-options\", &no_push_options,\n \t\t\t \"disable push options\"),\n-\t\tOPT_BOOL(0, \"die-version\", &die_version,\n-\t\t\t \"die during version negotiation\"),\n-\t\tOPT_BOOL(0, \"die-readline\", &die_readline,\n-\t\t\t \"die when readline\"),\n+\t\tOPT_BOOL(0, \"die-read-version\", &die_read_version,\n+\t\t\t \"die when reading version\"),\n+\t\tOPT_BOOL(0, \"die-write-version\", &die_write_version,\n+\t\t\t \"die when writing version\"),\n+\t\tOPT_BOOL(0, \"die-read-commands\", &die_read_commands,\n+\t\t\t \"die when reading commands\"),\n+\t\tOPT_BOOL(0, \"die-read-push-options\", &die_read_push_options,\n+\t\t\t \"die when reading push-options\"),\n+\t\tOPT_BOOL(0, \"die-write-report\", &die_write_report,\n+\t\t\t \"die when writing report\"),\n \t\tOPT_STRING_LIST('r', \"return\", &returns, \"old/new/ref/status/msg\",\n \t\t\t\t\"return of results\"),\n \t\tOPT__VERBOSE(&verbose, \"be verbose\"),\n@@ -136,7 +156,7 @@ int cmd__proc_receive(int argc, const char **argv)\n \t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n \tpacket_reader_init(&reader, 0, NULL, 0,\n \t\t\t   PACKET_READ_CHOMP_NEWLINE |\n-\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\t\t\t   PACKET_READ_GENTLE_ON_EOF);\n \n \tsigchain_push(SIGPIPE, SIG_IGN);\n \tproc_receive_verison(&reader);\n@@ -166,6 +186,8 @@ int cmd__proc_receive(int argc, const char **argv)\n \t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n \t}\n \n+\tif (die_write_report)\n+\t\tdie(\"die with the --die-write-report option\");\n \tif (returns.nr)\n \t\tfor_each_string_list_item(item, &returns)\n \t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\ndiff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh\nindex 854c3e884a..550ceaaf4b 100644\n--- a/t/t5411/test-0013-bad-protocol.sh\n+++ b/t/t5411/test-0013-bad-protocol.sh\n@@ -16,7 +16,8 @@ test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL)\" '\n \n \t# Check status report for git-push\n \tsed -n \\\n-\t\t-e \"/^To / { p; n; p; }\" \\\n+\t\t-e \"/^To / { p; }\" \\\n+\t\t-e \"/^ ! / { p; }\" \\\n \t\t<actual >actual-report &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n@@ -41,32 +42,98 @@ test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL)\" '\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"setup proc-receive hook (hook --die-version, $PROTOCOL)\" '\n+test_expect_success \"setup proc-receive hook (hook --die-read-version, $PROTOCOL)\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n-\ttest-tool proc-receive -v --die-version\n+\ttest-tool proc-receive -v --die-read-version\n \tEOF\n '\n \n # Refs of upstream : main(A)\n # Refs of workbench: main(A)  tags/v123\n # git push         :                       refs/for/main/topic(A)\n-test_expect_success \"proc-receive: bad protocol (hook --die-version, $PROTOCOL)\" '\n+test_expect_success \"proc-receive: bad protocol (hook --die-read-version, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/main/topic \\\n \t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output -n \\\n+\t\t-e \"/^To / { p; }\" \\\n+\t\t-e \"/^ ! / { p; }\" \\\n+\t\t<out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgrep \"remote: fatal: die with the --die-read-version option\" out &&\n+\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/main\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-write-version, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-write-version\n+\tEOF\n+'\n+\n+# Refs of upstream : main(A)\n+# Refs of workbench: main(A)  tags/v123\n+# git push         :                       refs/for/main/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-write-version, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/main/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output -n \\\n+\t\t-e \"/^To / { p; }\" \\\n+\t\t-e \"/^ ! / { p; }\" \\\n+\t\t<out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgrep \"remote: fatal: die with the --die-write-version option\" out &&\n+\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out &&\n \n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/main\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-read-commands, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-read-commands\n+\tEOF\n+'\n+\n+# Refs of upstream : main(A)\n+# Refs of workbench: main(A)  tags/v123\n+# git push         :                       refs/for/main/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-read-commands, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/main/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output -n \\\n+\t\t-e \"/^To / { p; }\" \\\n+\t\t-e \"/^ ! / { p; }\" \\\n+\t\t<out >actual &&\n \tcat >expect <<-EOF &&\n-\tremote: # pre-receive hook\n-\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n-\tremote: # proc-receive hook\n-\tremote: fatal: bad protocol version: 1\n-\tremote: error: proc-receive version \"0\" is not supported\n \tTo <URL/of/upstream.git>\n \t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n \tEOF\n \ttest_cmp expect actual &&\n+\tgrep \"remote: fatal: die with the --die-read-commands option\" out &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -76,23 +143,65 @@ test_expect_success \"proc-receive: bad protocol (hook --die-version, $PROTOCOL)\"\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"setup proc-receive hook (hook --die-readline, $PROTOCOL)\" '\n+test_expect_success \"setup proc-receive hook (hook --die-read-push-options, $PROTOCOL)\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n-\ttest-tool proc-receive -v --die-readline\n+\ttest-tool proc-receive -v --die-read-push-options\n \tEOF\n '\n \n # Refs of upstream : main(A)\n # Refs of workbench: main(A)  tags/v123\n # git push         :                       refs/for/main/topic(A)\n-test_expect_success \"proc-receive: bad protocol (hook --die-readline, $PROTOCOL)\" '\n+test_expect_success \"proc-receive: bad protocol (hook --die-read-push-options, $PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true &&\n \ttest_must_fail git -C workbench push origin \\\n+\t\t-o reviewers=user1,user2 \\\n \t\tHEAD:refs/for/main/topic \\\n \t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output -n \\\n+\t\t-e \"/^To / { p; }\" \\\n+\t\t-e \"/^ ! / { p; }\" \\\n+\t\t<out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgrep \"remote: fatal: die with the --die-read-push-options option\" out &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/main\n+\tEOF\n+\ttest_cmp expect actual\n+'\n \n-\tgrep \"remote: fatal: protocol error: expected \\\"old new ref\\\", got \\\"<ZERO-OID> <COMMIT-A> refs/for/main/topic\\\"\" actual &&\n+test_expect_success \"setup proc-receive hook (hook --die-write-report, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-write-report\n+\tEOF\n+'\n+\n+# Refs of upstream : main(A)\n+# Refs of workbench: main(A)  tags/v123\n+# git push         :                       refs/for/main/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-write-report, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/main/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output -n \\\n+\t\t-e \"/^To / { p; }\" \\\n+\t\t-e \"/^ ! / { p; }\" \\\n+\t\t<out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgrep \"remote: fatal: die with the --die-write-report option\" out &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -123,13 +232,15 @@ test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL)\" '\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n \tremote: # proc-receive hook\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n+\tremote: error: no response from proc-receive hook\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n \tTo <URL/of/upstream.git>\n \t * [new branch] HEAD -> next\n-\t ! [remote rejected] HEAD -> refs/for/main/topic (proc-receive failed to report status)\n+\t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n \tEOF\n \ttest_cmp expect actual &&\n+\n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n@@ -173,6 +284,7 @@ test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL)\" '\n \t ! [remote rejected] HEAD -> refs/for/main/topic (proc-receive failed to report status)\n \tEOF\n \ttest_cmp expect actual &&\n+\n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n@@ -208,6 +320,7 @@ test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL)\" '\n \t ! [remote rejected] HEAD -> refs/for/main/topic (proc-receive failed to report status)\n \tEOF\n \ttest_cmp expect actual &&\n+\n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\ndiff --git a/t/t5411/test-0014-bad-protocol--porcelain.sh b/t/t5411/test-0014-bad-protocol--porcelain.sh\nindex 88c56311da..5aeebe7553 100644\n--- a/t/t5411/test-0014-bad-protocol--porcelain.sh\n+++ b/t/t5411/test-0014-bad-protocol--porcelain.sh\n@@ -42,6 +42,175 @@ test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL/porc\n \ttest_cmp expect actual\n '\n \n+test_expect_success \"setup proc-receive hook (hook --die-read-version, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-read-version\n+\tEOF\n+'\n+\n+# Refs of upstream : main(A)\n+# Refs of workbench: main(A)  tags/v123\n+# git push         :                       refs/for/main/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-read-version, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/main/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output -n \\\n+\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n+\t\t<out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgrep \"remote: fatal: die with the --die-read-version option\" out &&\n+\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/main\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-write-version, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-write-version\n+\tEOF\n+'\n+\n+# Refs of upstream : main(A)\n+# Refs of workbench: main(A)  tags/v123\n+# git push         :                       refs/for/main/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-write-version, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/main/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output -n \\\n+\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n+\t\t<out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgrep \"remote: fatal: die with the --die-write-version option\" out &&\n+\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/main\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-read-commands, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-read-commands\n+\tEOF\n+'\n+\n+# Refs of upstream : main(A)\n+# Refs of workbench: main(A)  tags/v123\n+# git push         :                       refs/for/main/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-read-commands, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/main/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output -n \\\n+\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n+\t\t<out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgrep \"remote: fatal: die with the --die-read-commands option\" out &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/main\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-read-push-options, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-read-push-options\n+\tEOF\n+'\n+\n+# Refs of upstream : main(A)\n+# Refs of workbench: main(A)  tags/v123\n+# git push         :                       refs/for/main/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-read-push-options, $PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true &&\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t-o reviewers=user1,user2 \\\n+\t\tHEAD:refs/for/main/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output -n \\\n+\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n+\t\t<out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgrep \"remote: fatal: die with the --die-read-push-options option\" out &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/main\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-write-report, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-write-report\n+\tEOF\n+'\n+\n+# Refs of upstream : main(A)\n+# Refs of workbench: main(A)  tags/v123\n+# git push         :                       refs/for/main/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-write-report, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/main/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output -n \\\n+\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n+\t\t<out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgrep \"remote: fatal: die with the --die-write-report option\" out &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/main\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success \"setup proc-receive hook (no report, $PROTOCOL/porcelain)\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n@@ -63,14 +232,16 @@ test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL/porcelain)\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n \tremote: # proc-receive hook\n \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n+\tremote: error: no response from proc-receive hook\n \tremote: # post-receive hook\n \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n \tTo <URL/of/upstream.git>\n \t*    HEAD:refs/heads/next    [new branch]\n-\t!    HEAD:refs/for/main/topic    [remote rejected] (proc-receive failed to report status)\n+\t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n+\n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n@@ -84,7 +255,6 @@ test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL/porcelain)\n # Refs of workbench: main(A)  tags/v123\n test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n \tgit -C \"$upstream\" update-ref -d refs/heads/next\n-\n '\n \n test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL/porcelain)\" '\n@@ -115,6 +285,7 @@ test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n+\n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n@@ -151,6 +322,7 @@ test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL/porce\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n+\n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n-- \n2.29.0.dirty\n\n"},{"id":"409519","messageId":"xmqq7dqt6ilx.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20201110120135.42025-1-zhiyou.jx@alibaba-inc.com","subject":"Re: [PATCH v3 1/2] t5411: refactor make_user_friendly_and_stable_output","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-11-10T20:51:22Z","receivedAt":"2020-11-10T20:51:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> diff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\n> index 521a347710..b7cca2d8fb 100644\n> --- a/t/t5411/common-functions.sh\n> +++ b/t/t5411/common-functions.sh\n> @@ -42,7 +42,7 @@ create_commits_in () {\n>  make_user_friendly_and_stable_output () {\n>  \tsed \\\n>  \t\t-e \"s/  *\\$//\" \\\n> -\t\t-e \"s/   */ /g\" \\\n> +\t\t-e \"s/  */ /g\" \\\n>  \t\t-e \"s/'/\\\"/g\" \\\n>  \t\t-e \"s/\t/    /g\" \\\n>  \t\t-e \"s/$A/<COMMIT-A>/g\" \\\n> @@ -52,5 +52,11 @@ make_user_friendly_and_stable_output () {\n>  \t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n>  \t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\" \\\n>  \t\t-e \"s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#\" \\\n> -\t\t-e \"/^error: / d\"\n> +\t\t-e \"/^error: / d\" | \\\n> +\tif test $# -eq 0\n> +\tthen\n> +\t\tcat\n> +\telse\n> +\t\tsed ${1+\"$@\"}\n> +\tfi\n>  }\n\nI may have suggested it, but looking at this implementation I'd have\nto say it is not worth the extra process here.  The only reason why\nI made the suggestion was that way we can make the single \"sed\"\ninvocation to do what we want.\n\nIf you need custom output for just two tests in 5411-0000, define\nthe custom one that wraps make_user_friendly_and_stable_output in\nthat single script like so:\n\n    filter_out_remote_messages () {\n\tmake_user_friendly_and_stable_output |\n\tsed -n -e ...\n    }\n\nand then use that ...\n\n\n> diff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh\n> index 2b04b49367..b3af3f59b0 100644\n> --- a/t/t5411/test-0000-standard-git-push.sh\n> +++ b/t/t5411/test-0000-standard-git-push.sh\n> @@ -36,11 +36,10 @@ test_expect_success \"git-push --atomic ($PROTOCOL)\" '\n>  \t\tmain \\\n>  \t\t$B:refs/heads/next \\\n>  \t\t>out 2>&1 &&\n> -\tmake_user_friendly_and_stable_output <out |\n> -\t\tsed -n \\\n> -\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n> -\t\t\t-e \"/^ ! / { s/   */ /g; p; }\" \\\n> -\t\t\t>actual &&\n> +\tmake_user_friendly_and_stable_output -n \\\n> +\t\t-e \"/^To / { p; }\" \\\n> +\t\t-e \"/^ ! / { p; }\" \\\n> +\t\t<out >actual &&\n\n... perhaps like\n\n\tfilter_out_remote_messages <out >actual &&\t\n\nhere?\n\n> diff --git a/t/t5411/test-0001-standard-git-push--porcelain.sh b/t/t5411/test-0001-standard-git-push--porcelain.sh\n> index 747307f8da..16ff2d5666 100644\n> --- a/t/t5411/test-0001-standard-git-push--porcelain.sh\n> +++ b/t/t5411/test-0001-standard-git-push--porcelain.sh\n> @@ -37,16 +37,15 @@ test_expect_success \"git-push --atomic ($PROTOCOL/porcelain)\" '\n>  \t\tmain \\\n>  \t\t$B:refs/heads/next \\\n>  \t\t>out 2>&1 &&\n> -\tmake_user_friendly_and_stable_output <out |\n> -\t\tsed -n \\\n> -\t\t\t-e \"s/^# GETTEXT POISON #//\" \\\n> -\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n> -\t\t\t-e \"/^! / { s/   */ /g; p; }\" \\\n> -\t\t\t>actual &&\n> +\tmake_user_friendly_and_stable_output -n \\\n> +\t\t-e \"s/^# GETTEXT POISON #//\" \\\n> +\t\t-e \"/^To / { p; }\" \\\n> +\t\t-e \"/^! / { p; }\" \\\n> +\t\t<out >actual &&\n>  \tcat >expect <<-EOF &&\n>  \tTo <URL/of/upstream.git>\n> -\t! refs/heads/main:refs/heads/main [rejected] (non-fast-forward)\n> -\t! <COMMIT-B>:refs/heads/next [rejected] (atomic push failed)\n> +\t!    refs/heads/main:refs/heads/main    [rejected] (non-fast-forward)\n> +\t!    <COMMIT-B>:refs/heads/next    [rejected] (atomic push failed)\n>  \tEOF\n>  \ttest_cmp expect actual &&\n>  \tgit -C \"$upstream\" show-ref >out &&\n"},{"id":"409520","messageId":"xmqqzh3p53lt.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"CANYiYbH-x6khgTkkFV29+7AjghOZmG69_6-sQcm2489WMHOWAA@mail.gmail.com","subject":"Re: [PATCH v2] t5411: consistent result for proc-receive broken test","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-11-10T21:00:46Z","receivedAt":"2020-11-10T21:00:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> Thanks Peff for pointing the root cause. Will use the \"gently\" forms\n> of packet_write_fmt() and packet_flush() in patch v3.\n\nAnother thing I noticed.  Its tests are based on a codebase where\nmaster is called main, which means it is not designed to be usable\nwith the 'master' or 'maint' track.  \n\nBecause the topic is no longer \"just apply a band-aid to paper over\ntest flakeyness\" but is fixing the codepath that matters in real\nuser experience, I'd prefer to see it applicable even to the\nmaintenance track.\n\nThanks.\n"},{"id":"409521","messageId":"xmqqv9ec6hkq.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"xmqqzh3p53lt.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v2] t5411: consistent result for proc-receive broken test","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-11-10T21:13:41Z","receivedAt":"2020-11-10T21:13:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Jiang Xin <worldhello.net@gmail.com> writes:\n>\n>> Thanks Peff for pointing the root cause. Will use the \"gently\" forms\n>> of packet_write_fmt() and packet_flush() in patch v3.\n>\n> Another thing I noticed.  Its tests are based on a codebase where\n> master is called main, which means it is not designed to be usable\n> with the 'master' or 'maint' track.  \n>\n> Because the topic is no longer \"just apply a band-aid to paper over\n> test flakeyness\" but is fixing the codepath that matters in real\n> user experience, I'd prefer to see it applicable even to the\n> maintenance track.\n\nI've queued these patches on top of\njs/default-branch-name-adjust-t5411; that makes the topic mergeable\nto 'master' but not to 'maint'.  Perhaps that is good enough, as the\nmaintenance track of today would become obsolete by the end of this\nyear.\n\nThanks.\n"},{"id":"409555","messageId":"20201110215250.GB3263091@coredump.intra.peff.net","threadId":"52936","inReplyTo":"20201110120135.42025-2-zhiyou.jx@alibaba-inc.com","subject":"Re: [PATCH v3 2/2] receive-pack: gently write messages to proc-receive","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-10T21:52:50Z","receivedAt":"2020-11-10T21:52:53Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Nov 10, 2020 at 08:01:35PM +0800, Jiang Xin wrote:\n\n> Johannes found a flaky hang in `t5411/test-0013-bad-protocol.sh` in the\n> osx-clang job of the CI/PR builds, and ran into an issue when using\n> the `--stress` option with the following error messages:\n> \n>     fatal: unable to write flush packet: Broken pipe\n>     send-pack: unexpected disconnect while reading sideband packet\n>     fatal: the remote end hung up unexpectedly\n> \n> In this test case, the \"proc-receive\" hook sends an error message and\n> dies earlier. While \"receive-pack\" on the other side of the pipe\n> should forward the error message of the \"proc-receive\" hook to the\n> client side, but it fails to do so. This is because \"receive-pack\"\n> uses `packet_write_fmt()` and `packet_flush()` to write pkt-line\n> message to \"proc-receive\" hook, and these functions die immediately\n> when pipe is broken. Using \"gently\" forms for these functions will get\n> more predicable output.\n\nThe changes to use gently() in the code looked good to me, and I think\nyou got all of the relevant spots.\n\nI was surprised by a few bits:\n\n> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\n> index bb9909c52e..697a4e8802 100644\n> --- a/builtin/receive-pack.c\n> +++ b/builtin/receive-pack.c\n> @@ -974,9 +974,10 @@ static int read_proc_receive_report(struct packet_reader *reader,\n>  \tstruct command *cmd;\n>  \tstruct command *hint = NULL;\n>  \tstruct ref_push_report *report = NULL;\n> -\tint new_report = 0;\n>  \tint code = 0;\n> +\tint new_report = 0;\n\nThis is just noise in the diff, I think. It does not matter either way.\n\n> @@ -984,8 +985,14 @@ static int read_proc_receive_report(struct packet_reader *reader,\n>  \t\tconst char *refname;\n>  \t\tchar *p;\n>  \n> -\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n> +\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n> +\t\t\tif (!response) {\n> +\t\t\t\tstrbuf_addstr(errmsg, \"no response from proc-receive hook\");\n> +\t\t\t\treturn -1;\n> +\t\t\t}\n>  \t\t\tbreak;\n> +\t\t}\n> +\t\tresponse++;\n\nThis extra check seems orthogonal to the rest of the commit. It does\nseem like it might be a reasonable thing to check, but I wondered:\n\n  - if the hook has nothing to report, wouldn't it just send a flush\n    packet? Does that break protocol or not?\n\n  - if not, then I guess we're expecting a response for every ref we\n    mentioned (and presumably we would not trigger the hook at all if\n    there are no refs). But in that case, shouldn't we be checking that\n    we counted up the number of responses we expected? But we already do\n    that, by annotating the items in the commands list that didn't get\n    RUN_PROC_RECEIVE_RETURNED set.\n\nSo at best, it seems like this check is redundant (and at worst it may\ncomplain unnecessarily about a corner case).\n\n> @@ -1100,7 +1107,7 @@ static int run_proc_receive_hook(struct command *commands,\n>  \tstruct strbuf cap = STRBUF_INIT;\n>  \tstruct strbuf errmsg = STRBUF_INIT;\n>  \tint hook_use_push_options = 0;\n> -\tint version = 0;\n> +\tint version = -1;\n> [...]\n> -\tif (version != 1) {\n> +\tif (version == -1) {\n> +\t\tstrbuf_addstr(&errmsg, \"fail to negotiate version with proc-receive hook\");\n> +\t\tcode = -1;\n> +\t\tgoto cleanup;\n> +\t} else if (version != 1) {\n>  \t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n>  \t\t\t    version);\n\nLikewise this seems orthogonal to the main point of the patch. Though it\nseems like a good idea in general to check when the other side doesn't\nreport a version (assuming it is a protocol breakage not to report the\nversion, and we're not simply supposed to default).\n\n-Peff\n"},{"id":"409599","messageId":"CANYiYbEd1bDtwsq44Xqsc1o3=a6XO7aX5b_ZQ9gh7-xOS5O5rg@mail.gmail.com","threadId":"52936","inReplyTo":"20201110215250.GB3263091@coredump.intra.peff.net","subject":"Re: [PATCH v3 2/2] receive-pack: gently write messages to proc-receive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-11-11T11:03:51Z","receivedAt":"2020-11-11T11:04:05Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Jeff King <peff@peff.net> 于2020年11月11日周三 上午5:52写道：\n> > diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\n> > index bb9909c52e..697a4e8802 100644\n> > --- a/builtin/receive-pack.c\n> > +++ b/builtin/receive-pack.c\n> > @@ -974,9 +974,10 @@ static int read_proc_receive_report(struct packet_reader *reader,\n> >       struct command *cmd;\n> >       struct command *hint = NULL;\n> >       struct ref_push_report *report = NULL;\n> > -     int new_report = 0;\n> >       int code = 0;\n> > +     int new_report = 0;\n>\n> This is just noise in the diff, I think. It does not matter either way.\n>\n> > @@ -984,8 +985,14 @@ static int read_proc_receive_report(struct packet_reader *reader,\n> >               const char *refname;\n> >               char *p;\n> >\n> > -             if (packet_reader_read(reader) != PACKET_READ_NORMAL)\n> > +             if (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n> > +                     if (!response) {\n> > +                             strbuf_addstr(errmsg, \"no response from proc-receive hook\");\n> > +                             return -1;\n> > +                     }\n> >                       break;\n> > +             }\n> > +             response++;\n>\n> This extra check seems orthogonal to the rest of the commit. It does\n> seem like it might be a reasonable thing to check, but I wondered:\n>\n>   - if the hook has nothing to report, wouldn't it just send a flush\n>     packet? Does that break protocol or not?\n\nIt's OK to send a flush packet without any payload, so will check\nstatus PACKET_READ_EOF for broken proc-receive.\n\n-- snip --\n@@ -977,15 +977,25 @@ static int read_proc_receive_report(struct\npacket_reader *reader,\n        for (;;) {\n                struct object_id old_oid, new_oid;\n                const char *head;\n                const char *refname;\n                char *p;\n-\n-               if (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+               enum packet_read_status status;\n+\n+               status = packet_reader_read(reader);\n+               if (status != PACKET_READ_NORMAL) {\n+                       /* Check whether proc-receive exited abnormally */\n+                       if (status == PACKET_READ_EOF && !response) {\n+                               strbuf_addstr(errmsg, \"proc-receive\nexited abnormally\");\n+                               return -1;\n+                       }\n                        break;\n+               }\n+               response++;\n-- snap --\n\n>\n> > @@ -1100,7 +1107,7 @@ static int run_proc_receive_hook(struct command *commands,\n> >       struct strbuf cap = STRBUF_INIT;\n> >       struct strbuf errmsg = STRBUF_INIT;\n> >       int hook_use_push_options = 0;\n> > -     int version = 0;\n> > +     int version = -1;\n> > [...]\n> > -     if (version != 1) {\n> > +     if (version == -1) {\n> > +             strbuf_addstr(&errmsg, \"fail to negotiate version with proc-receive hook\");\n> > +             code = -1;\n> > +             goto cleanup;\n> > +     } else if (version != 1) {\n> >               strbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n> >                           version);\n>\n> Likewise this seems orthogonal to the main point of the patch. Though it\n> seems like a good idea in general to check when the other side doesn't\n> report a version (assuming it is a protocol breakage not to report the\n> version, and we're not simply supposed to default).\n\nWill add a new patch for default version for proc-receive.\n"},{"id":"409600","messageId":"CANYiYbEFrqCDOcmFek336UG7PK0TLUaa26jVYjRgRcCvtfkHsg@mail.gmail.com","threadId":"52936","inReplyTo":"xmqq7dqt6ilx.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v3 1/2] t5411: refactor make_user_friendly_and_stable_output","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-11-11T11:08:03Z","receivedAt":"2020-11-11T11:08:19Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2020年11月11日周三 上午4:51写道：\n>\n> Jiang Xin <worldhello.net@gmail.com> writes:\n>\n> > diff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\n> > index 521a347710..b7cca2d8fb 100644\n> > --- a/t/t5411/common-functions.sh\n> > +++ b/t/t5411/common-functions.sh\n> > @@ -42,7 +42,7 @@ create_commits_in () {\n> >  make_user_friendly_and_stable_output () {\n> >       sed \\\n> >               -e \"s/  *\\$//\" \\\n> > -             -e \"s/   */ /g\" \\\n> > +             -e \"s/  */ /g\" \\\n> >               -e \"s/'/\\\"/g\" \\\n> >               -e \"s/  /    /g\" \\\n> >               -e \"s/$A/<COMMIT-A>/g\" \\\n> > @@ -52,5 +52,11 @@ make_user_friendly_and_stable_output () {\n> >               -e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n> >               -e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\" \\\n> >               -e \"s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#\" \\\n> > -             -e \"/^error: / d\"\n> > +             -e \"/^error: / d\" | \\\n> > +     if test $# -eq 0\n> > +     then\n> > +             cat\n> > +     else\n> > +             sed ${1+\"$@\"}\n> > +     fi\n> >  }\n>\n> I may have suggested it, but looking at this implementation I'd have\n> to say it is not worth the extra process here.  The only reason why\n> I made the suggestion was that way we can make the single \"sed\"\n> invocation to do what we want.\n>\n> If you need custom output for just two tests in 5411-0000, define\n> the custom one that wraps make_user_friendly_and_stable_output in\n> that single script like so:\n>\n>     filter_out_remote_messages () {\n>         make_user_friendly_and_stable_output |\n>         sed -n -e ...\n>     }\n>\n> and then use that ...\n\nWill add new helper function\n`filter_out_user_friendly_and_stable_output` like this, and use it for\n5411-0000, 5411-0001, 5411-0013 and 5411-0014.\n"},{"id":"409601","messageId":"20201111113202.24911-2-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"CANYiYbH-x6khgTkkFV29+7AjghOZmG69_6-sQcm2489WMHOWAA@mail.gmail.com","subject":"[PATCH v4 1/3] t5411: new helper filter_out_user_friendly_and_stable_output","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-11-11T11:32:00Z","receivedAt":"2020-11-11T11:32:24Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <worldhello.net@gmail.com>\n\nNew helper `filter_out_user_friendly_and_stable_output` will call\ncommon helpr function `make_user_friendly_and_stable_output` and use\nadditional arguments to filter out messages for specific test cases.\n\nSuggested-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411/common-functions.sh                       |  7 ++++++-\n t/t5411/test-0000-standard-git-push.sh            |  9 ++++-----\n t/t5411/test-0001-standard-git-push--porcelain.sh | 15 +++++++--------\n 3 files changed, 17 insertions(+), 14 deletions(-)\n\ndiff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\nindex 521a347710..344d13f61a 100644\n--- a/t/t5411/common-functions.sh\n+++ b/t/t5411/common-functions.sh\n@@ -42,7 +42,7 @@ create_commits_in () {\n make_user_friendly_and_stable_output () {\n \tsed \\\n \t\t-e \"s/  *\\$//\" \\\n-\t\t-e \"s/   */ /g\" \\\n+\t\t-e \"s/  */ /g\" \\\n \t\t-e \"s/'/\\\"/g\" \\\n \t\t-e \"s/\t/    /g\" \\\n \t\t-e \"s/$A/<COMMIT-A>/g\" \\\n@@ -54,3 +54,8 @@ make_user_friendly_and_stable_output () {\n \t\t-e \"s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#\" \\\n \t\t-e \"/^error: / d\"\n }\n+\n+filter_out_user_friendly_and_stable_output () {\n+\tmake_user_friendly_and_stable_output |\n+\t\tsed -n ${1+\"$@\"}\n+}\ndiff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh\nindex 2b04b49367..47b058af7e 100644\n--- a/t/t5411/test-0000-standard-git-push.sh\n+++ b/t/t5411/test-0000-standard-git-push.sh\n@@ -36,11 +36,10 @@ test_expect_success \"git-push --atomic ($PROTOCOL)\" '\n \t\tmain \\\n \t\t$B:refs/heads/next \\\n \t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out |\n-\t\tsed -n \\\n-\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n-\t\t\t-e \"/^ ! / { s/   */ /g; p; }\" \\\n-\t\t\t>actual &&\n+\tfilter_out_user_friendly_and_stable_output \\\n+\t\t-e \"/^To / { p; }\" \\\n+\t\t-e \"/^ ! / { p; }\" \\\n+\t\t<out >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t ! [rejected] main -> main (non-fast-forward)\ndiff --git a/t/t5411/test-0001-standard-git-push--porcelain.sh b/t/t5411/test-0001-standard-git-push--porcelain.sh\nindex 747307f8da..bbead12bbb 100644\n--- a/t/t5411/test-0001-standard-git-push--porcelain.sh\n+++ b/t/t5411/test-0001-standard-git-push--porcelain.sh\n@@ -37,16 +37,15 @@ test_expect_success \"git-push --atomic ($PROTOCOL/porcelain)\" '\n \t\tmain \\\n \t\t$B:refs/heads/next \\\n \t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out |\n-\t\tsed -n \\\n-\t\t\t-e \"s/^# GETTEXT POISON #//\" \\\n-\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n-\t\t\t-e \"/^! / { s/   */ /g; p; }\" \\\n-\t\t\t>actual &&\n+\tfilter_out_user_friendly_and_stable_output \\\n+\t\t-e \"s/^# GETTEXT POISON #//\" \\\n+\t\t-e \"/^To / { p; }\" \\\n+\t\t-e \"/^! / { p; }\" \\\n+\t\t<out >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n-\t! refs/heads/main:refs/heads/main [rejected] (non-fast-forward)\n-\t! <COMMIT-B>:refs/heads/next [rejected] (atomic push failed)\n+\t!    refs/heads/main:refs/heads/main    [rejected] (non-fast-forward)\n+\t!    <COMMIT-B>:refs/heads/next    [rejected] (atomic push failed)\n \tEOF\n \ttest_cmp expect actual &&\n \tgit -C \"$upstream\" show-ref >out &&\n-- \n2.29.0.dirty\n\n"},{"id":"409602","messageId":"20201111113202.24911-1-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"CANYiYbH-x6khgTkkFV29+7AjghOZmG69_6-sQcm2489WMHOWAA@mail.gmail.com","subject":"[PATCH v4 0/3] jx/t5411-flake-fix","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-11-11T11:31:59Z","receivedAt":"2020-11-11T11:32:26Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"The exchange between receive-pack and proc-receive hook did not\ncarefully check for errors.\n\n## Changes since v3\n\n1. Patch 1/3:\n\n   Add new helper function `filter_out_user_friendly_and_stable_output`,\n   and use it for 5411-0000, 5411-0001, 5411-0013 and 5411-0014.\n\n2. Patch 2/3:\n\n   Check PACKET_READ_EOF status from reader for broken pipe.\n\n3. Patch 3/3:\n\n   Add a new patch, which uses version 0 for proc-receive by default.\n\n\n## Range-diff of v3...v4\n\n1:  785b28333f ! 1:  a3bdb5a5d6 t5411: refactor make_user_friendly_and_stable_output\n    @@ Metadata\n     Author: Jiang Xin <worldhello.net@gmail.com>\n     \n      ## Commit message ##\n    -    t5411: refactor make_user_friendly_and_stable_output\n    +    t5411: new helper filter_out_user_friendly_and_stable_output\n     \n    -    Common function `make_user_friendly_and_stable_output()` can take\n    -    additional arguments to format messages for special cases.\n    +    New helper `filter_out_user_friendly_and_stable_output` will call\n    +    common helpr function `make_user_friendly_and_stable_output` and use\n    +    additional arguments to filter out messages for specific test cases.\n     \n         Suggested-by: Junio C Hamano <gitster@pobox.com>\n         Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n     \n      ## t/t5411/common-functions.sh ##\n     @@ t/t5411/common-functions.sh: create_commits_in () {\n    @@ t/t5411/common-functions.sh: create_commits_in () {\n      \t\t-e \"s/\t/    /g\" \\\n      \t\t-e \"s/$A/<COMMIT-A>/g\" \\\n     @@ t/t5411/common-functions.sh: make_user_friendly_and_stable_output () {\n    - \t\t-e \"s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g\" \\\n    - \t\t-e \"s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g\" \\\n      \t\t-e \"s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#\" \\\n    --\t\t-e \"/^error: / d\"\n    -+\t\t-e \"/^error: / d\" | \\\n    -+\tif test $# -eq 0\n    -+\tthen\n    -+\t\tcat\n    -+\telse\n    -+\t\tsed ${1+\"$@\"}\n    -+\tfi\n    + \t\t-e \"/^error: / d\"\n      }\n    ++\n    ++filter_out_user_friendly_and_stable_output () {\n    ++\tmake_user_friendly_and_stable_output |\n    ++\t\tsed -n ${1+\"$@\"}\n    ++}\n     \n      ## t/t5411/test-0000-standard-git-push.sh ##\n     @@ t/t5411/test-0000-standard-git-push.sh: test_expect_success \"git-push --atomic ($PROTOCOL)\" '\n    @@ t/t5411/test-0000-standard-git-push.sh: test_expect_success \"git-push --atomic (\n     -\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n     -\t\t\t-e \"/^ ! / { s/   */ /g; p; }\" \\\n     -\t\t\t>actual &&\n    -+\tmake_user_friendly_and_stable_output -n \\\n    ++\tfilter_out_user_friendly_and_stable_output \\\n     +\t\t-e \"/^To / { p; }\" \\\n     +\t\t-e \"/^ ! / { p; }\" \\\n     +\t\t<out >actual &&\n    @@ t/t5411/test-0001-standard-git-push--porcelain.sh: test_expect_success \"git-push\n     -\t\t\t-e \"/^To / { s/   */ /g; p; }\" \\\n     -\t\t\t-e \"/^! / { s/   */ /g; p; }\" \\\n     -\t\t\t>actual &&\n    -+\tmake_user_friendly_and_stable_output -n \\\n    ++\tfilter_out_user_friendly_and_stable_output \\\n     +\t\t-e \"s/^# GETTEXT POISON #//\" \\\n     +\t\t-e \"/^To / { p; }\" \\\n     +\t\t-e \"/^! / { p; }\" \\\n2:  d83d4519db ! 2:  4ca1c6e2bc receive-pack: gently write messages to proc-receive\n    @@ Commit message\n         Reported-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n         Suggested-by: Jeff King <peff@peff.net>\n         Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n     \n      ## builtin/receive-pack.c ##\n     @@ builtin/receive-pack.c: static int read_proc_receive_report(struct packet_reader *reader,\n    - \tstruct command *cmd;\n    - \tstruct command *hint = NULL;\n    - \tstruct ref_push_report *report = NULL;\n    --\tint new_report = 0;\n    + \tint new_report = 0;\n      \tint code = 0;\n    -+\tint new_report = 0;\n      \tint once = 0;\n     +\tint response = 0;\n      \n      \tfor (;;) {\n      \t\tstruct object_id old_oid, new_oid;\n    -@@ builtin/receive-pack.c: static int read_proc_receive_report(struct packet_reader *reader,\n    + \t\tconst char *head;\n      \t\tconst char *refname;\n      \t\tchar *p;\n    - \n    +-\n     -\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n    -+\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL) {\n    -+\t\t\tif (!response) {\n    -+\t\t\t\tstrbuf_addstr(errmsg, \"no response from proc-receive hook\");\n    ++\t\tenum packet_read_status status;\n    ++\n    ++\t\tstatus = packet_reader_read(reader);\n    ++\t\tif (status != PACKET_READ_NORMAL) {\n    ++\t\t\t/* Check whether proc-receive exited abnormally */\n    ++\t\t\tif (status == PACKET_READ_EOF && !response) {\n    ++\t\t\t\tstrbuf_addstr(errmsg, \"proc-receive exited abnormally\");\n     +\t\t\t\treturn -1;\n     +\t\t\t}\n      \t\t\tbreak;\n    @@ builtin/receive-pack.c: static int read_proc_receive_report(struct packet_reader\n      \n      \t\thead = reader->line;\n      \t\tp = strchr(head, ' ');\n    -@@ builtin/receive-pack.c: static int run_proc_receive_hook(struct command *commands,\n    - \tstruct strbuf cap = STRBUF_INIT;\n    - \tstruct strbuf errmsg = STRBUF_INIT;\n    - \tint hook_use_push_options = 0;\n    --\tint version = 0;\n    -+\tint version = -1;\n    - \tint code;\n    - \n    - \targv[0] = find_hook(\"proc-receive\");\n     @@ builtin/receive-pack.c: static int run_proc_receive_hook(struct command *commands,\n      \tif (use_push_options)\n      \t\tstrbuf_addstr(&cap, \" push-options\");\n    @@ builtin/receive-pack.c: static int run_proc_receive_hook(struct command *command\n      \t} else {\n     -\t\tpacket_write_fmt(proc.in, \"version=1\\n\");\n     +\t\tcode = packet_write_fmt_gently(proc.in, \"version=1\\n\");\n    -+\t}\n    + \t}\n    +-\tpacket_flush(proc.in);\n     +\tif (!code)\n     +\t\tcode = packet_flush_gently(proc.in);\n    + \n    +-\tfor (;;) {\n    +-\t\tint linelen;\n    ++\tif (!code)\n    ++\t\tfor (;;) {\n    ++\t\t\tint linelen;\n    ++\t\t\tenum packet_read_status status;\n    + \n    +-\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL)\n    +-\t\t\tbreak;\n    ++\t\t\tstatus = packet_reader_read(&reader);\n    ++\t\t\tif (status != PACKET_READ_NORMAL) {\n    ++\t\t\t\t/* Check whether proc-receive exited abnormally */\n    ++\t\t\t\tif (status == PACKET_READ_EOF)\n    ++\t\t\t\t\tcode = -1;\n    ++\t\t\t\tbreak;\n    ++\t\t\t}\n    + \n    +-\t\tif (reader.pktlen > 8 && starts_with(reader.line, \"version=\")) {\n    +-\t\t\tversion = atoi(reader.line + 8);\n    +-\t\t\tlinelen = strlen(reader.line);\n    +-\t\t\tif (linelen < reader.pktlen) {\n    +-\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n    +-\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n    +-\t\t\t\t\thook_use_push_options = 1;\n    ++\t\t\tif (reader.pktlen > 8 && starts_with(reader.line, \"version=\")) {\n    ++\t\t\t\tversion = atoi(reader.line + 8);\n    ++\t\t\t\tlinelen = strlen(reader.line);\n    ++\t\t\t\tif (linelen < reader.pktlen) {\n    ++\t\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n    ++\t\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n    ++\t\t\t\t\t\thook_use_push_options = 1;\n    ++\t\t\t\t}\n    + \t\t\t}\n    + \t\t}\n    ++\n     +\tif (code) {\n     +\t\tstrbuf_addstr(&errmsg, \"fail to negotiate version with proc-receive hook\");\n     +\t\tgoto cleanup;\n      \t}\n    --\tpacket_flush(proc.in);\n      \n    - \tfor (;;) {\n    - \t\tint linelen;\n    -@@ builtin/receive-pack.c: static int run_proc_receive_hook(struct command *commands,\n    - \t\t}\n    - \t}\n    - \n    --\tif (version != 1) {\n    -+\tif (version == -1) {\n    -+\t\tstrbuf_addstr(&errmsg, \"fail to negotiate version with proc-receive hook\");\n    -+\t\tcode = -1;\n    -+\t\tgoto cleanup;\n    -+\t} else if (version != 1) {\n    - \t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n    - \t\t\t    version);\n    - \t\tcode = -1;\n    + \tif (version != 1) {\n     @@ builtin/receive-pack.c: static int run_proc_receive_hook(struct command *commands,\n      \tfor (cmd = commands; cmd; cmd = cmd->next) {\n      \t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)\n    @@ t/t5411/test-0013-bad-protocol.sh: test_expect_success \"proc-receive: bad protoc\n      \ttest_must_fail git -C workbench push origin \\\n      \t\tHEAD:refs/for/main/topic \\\n      \t\t>out 2>&1 &&\n    -+\tmake_user_friendly_and_stable_output -n \\\n    ++\tfilter_out_user_friendly_and_stable_output \\\n     +\t\t-e \"/^To / { p; }\" \\\n     +\t\t-e \"/^ ! / { p; }\" \\\n     +\t\t<out >actual &&\n    @@ t/t5411/test-0013-bad-protocol.sh: test_expect_success \"proc-receive: bad protoc\n     +\ttest-tool proc-receive -v --die-write-version\n     +\tEOF\n     +'\n    -+\n    + \n     +# Refs of upstream : main(A)\n     +# Refs of workbench: main(A)  tags/v123\n     +# git push         :                       refs/for/main/topic(A)\n    @@ t/t5411/test-0013-bad-protocol.sh: test_expect_success \"proc-receive: bad protoc\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:refs/for/main/topic \\\n     +\t\t>out 2>&1 &&\n    -+\tmake_user_friendly_and_stable_output -n \\\n    ++\tfilter_out_user_friendly_and_stable_output \\\n     +\t\t-e \"/^To / { p; }\" \\\n     +\t\t-e \"/^ ! / { p; }\" \\\n     +\t\t<out >actual &&\n    @@ t/t5411/test-0013-bad-protocol.sh: test_expect_success \"proc-receive: bad protoc\n     +\ttest_cmp expect actual &&\n     +\tgrep \"remote: fatal: die with the --die-write-version option\" out &&\n     +\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out &&\n    - \n    ++\n     +\tgit -C \"$upstream\" show-ref >out &&\n     +\tmake_user_friendly_and_stable_output <out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5411/test-0013-bad-protocol.sh: test_expect_success \"proc-receive: bad protoc\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:refs/for/main/topic \\\n     +\t\t>out 2>&1 &&\n    -+\tmake_user_friendly_and_stable_output -n \\\n    ++\tfilter_out_user_friendly_and_stable_output \\\n     +\t\t-e \"/^To / { p; }\" \\\n     +\t\t-e \"/^ ! / { p; }\" \\\n     +\t\t<out >actual &&\n    @@ t/t5411/test-0013-bad-protocol.sh: test_expect_success \"proc-receive: bad protoc\n     +\t\t-o reviewers=user1,user2 \\\n      \t\tHEAD:refs/for/main/topic \\\n      \t\t>out 2>&1 &&\n    -+\tmake_user_friendly_and_stable_output -n \\\n    ++\tfilter_out_user_friendly_and_stable_output \\\n     +\t\t-e \"/^To / { p; }\" \\\n     +\t\t-e \"/^ ! / { p; }\" \\\n     +\t\t<out >actual &&\n    @@ t/t5411/test-0013-bad-protocol.sh: test_expect_success \"proc-receive: bad protoc\n     +\tEOF\n     +\ttest_cmp expect actual\n     +'\n    - \n    --\tgrep \"remote: fatal: protocol error: expected \\\"old new ref\\\", got \\\"<ZERO-OID> <COMMIT-A> refs/for/main/topic\\\"\" actual &&\n    ++\n     +test_expect_success \"setup proc-receive hook (hook --die-write-report, $PROTOCOL)\" '\n     +\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n     +\tprintf >&2 \"# proc-receive hook\\n\"\n     +\ttest-tool proc-receive -v --die-write-report\n     +\tEOF\n     +'\n    -+\n    + \n    +-\tgrep \"remote: fatal: protocol error: expected \\\"old new ref\\\", got \\\"<ZERO-OID> <COMMIT-A> refs/for/main/topic\\\"\" actual &&\n     +# Refs of upstream : main(A)\n     +# Refs of workbench: main(A)  tags/v123\n     +# git push         :                       refs/for/main/topic(A)\n    @@ t/t5411/test-0013-bad-protocol.sh: test_expect_success \"proc-receive: bad protoc\n     +\ttest_must_fail git -C workbench push origin \\\n     +\t\tHEAD:refs/for/main/topic \\\n     +\t\t>out 2>&1 &&\n    -+\tmake_user_friendly_and_stable_output -n \\\n    ++\tfilter_out_user_friendly_and_stable_output \\\n     +\t\t-e \"/^To / { p; }\" \\\n     +\t\t-e \"/^ ! / { p; }\" \\\n     +\t\t<out >actual &&\n    @@ t/t5411/test-0013-bad-protocol.sh: test_expect_success \"proc-receive: bad protoc\n      \tgit -C \"$upstream\" show-ref >out &&\n      \tmake_user_friendly_and_stable_output <out >actual &&\n     @@ t/t5411/test-0013-bad-protocol.sh: test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL)\" '\n    - \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n    - \tremote: # proc-receive hook\n    - \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n    -+\tremote: error: no response from proc-receive hook\n    - \tremote: # post-receive hook\n    - \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n    - \tTo <URL/of/upstream.git>\n    - \t * [new branch] HEAD -> next\n    --\t ! [remote rejected] HEAD -> refs/for/main/topic (proc-receive failed to report status)\n    -+\t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n    + \t ! [remote rejected] HEAD -> refs/for/main/topic (proc-receive failed to report status)\n      \tEOF\n      \ttest_cmp expect actual &&\n     +\n    @@ t/t5411/test-0014-bad-protocol--porcelain.sh: test_expect_success \"proc-receive:\n     +\ttest_must_fail git -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/main/topic \\\n     +\t\t>out 2>&1 &&\n    -+\tmake_user_friendly_and_stable_output -n \\\n    ++\tfilter_out_user_friendly_and_stable_output \\\n     +\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n     +\t\t<out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5411/test-0014-bad-protocol--porcelain.sh: test_expect_success \"proc-receive:\n     +\ttest_must_fail git -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/main/topic \\\n     +\t\t>out 2>&1 &&\n    -+\tmake_user_friendly_and_stable_output -n \\\n    ++\tfilter_out_user_friendly_and_stable_output \\\n     +\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n     +\t\t<out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5411/test-0014-bad-protocol--porcelain.sh: test_expect_success \"proc-receive:\n     +\ttest_must_fail git -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/main/topic \\\n     +\t\t>out 2>&1 &&\n    -+\tmake_user_friendly_and_stable_output -n \\\n    ++\tfilter_out_user_friendly_and_stable_output \\\n     +\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n     +\t\t<out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5411/test-0014-bad-protocol--porcelain.sh: test_expect_success \"proc-receive:\n     +\t\t-o reviewers=user1,user2 \\\n     +\t\tHEAD:refs/for/main/topic \\\n     +\t\t>out 2>&1 &&\n    -+\tmake_user_friendly_and_stable_output -n \\\n    ++\tfilter_out_user_friendly_and_stable_output \\\n     +\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n     +\t\t<out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5411/test-0014-bad-protocol--porcelain.sh: test_expect_success \"proc-receive:\n     +\ttest_must_fail git -C workbench push --porcelain origin \\\n     +\t\tHEAD:refs/for/main/topic \\\n     +\t\t>out 2>&1 &&\n    -+\tmake_user_friendly_and_stable_output -n \\\n    ++\tfilter_out_user_friendly_and_stable_output \\\n     +\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n     +\t\t<out >actual &&\n     +\tcat >expect <<-EOF &&\n    @@ t/t5411/test-0014-bad-protocol--porcelain.sh: test_expect_success \"proc-receive:\n      \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n      \tprintf >&2 \"# proc-receive hook\\n\"\n     @@ t/t5411/test-0014-bad-protocol--porcelain.sh: test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL/porcelain)\n    - \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n    - \tremote: # proc-receive hook\n    - \tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n    -+\tremote: error: no response from proc-receive hook\n    - \tremote: # post-receive hook\n    - \tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n    - \tTo <URL/of/upstream.git>\n    - \t*    HEAD:refs/heads/next    [new branch]\n    --\t!    HEAD:refs/for/main/topic    [remote rejected] (proc-receive failed to report status)\n    -+\t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n      \tDone\n      \tEOF\n      \ttest_cmp expect actual &&\n-:  ---------- > 3:  82d03e6508 receive-pack: use default version 0 for proc-receive\n\n---\n\nJiang Xin (3):\n  t5411: new helper filter_out_user_friendly_and_stable_output\n  receive-pack: gently write messages to proc-receive\n  receive-pack: use default version 0 for proc-receive\n\n builtin/receive-pack.c                        |  94 +++++++---\n t/helper/test-proc-receive.c                  |  54 ++++--\n t/t5411/common-functions.sh                   |   7 +-\n t/t5411/test-0000-standard-git-push.sh        |   9 +-\n .../test-0001-standard-git-push--porcelain.sh |  15 +-\n t/t5411/test-0013-bad-protocol.sh             | 138 ++++++++++++--\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 173 +++++++++++++++++-\n t/t5411/test-0026-push-options.sh             |  60 ++++++\n t/t5411/test-0027-push-options--porcelain.sh  |  62 +++++++\n 9 files changed, 545 insertions(+), 67 deletions(-)\n\n-- \n2.29.0.dirty\n\n"},{"id":"409603","messageId":"20201111113202.24911-3-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"CANYiYbH-x6khgTkkFV29+7AjghOZmG69_6-sQcm2489WMHOWAA@mail.gmail.com","subject":"[PATCH v4 2/3] receive-pack: gently write messages to proc-receive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-11-11T11:32:01Z","receivedAt":"2020-11-11T11:32:33Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <worldhello.net@gmail.com>\n\nJohannes found a flaky hang in `t5411/test-0013-bad-protocol.sh` in the\nosx-clang job of the CI/PR builds, and ran into an issue when using\nthe `--stress` option with the following error messages:\n\n    fatal: unable to write flush packet: Broken pipe\n    send-pack: unexpected disconnect while reading sideband packet\n    fatal: the remote end hung up unexpectedly\n\nIn this test case, the \"proc-receive\" hook sends an error message and\ndies earlier. While \"receive-pack\" on the other side of the pipe\nshould forward the error message of the \"proc-receive\" hook to the\nclient side, but it fails to do so. This is because \"receive-pack\"\nuses `packet_write_fmt()` and `packet_flush()` to write pkt-line\nmessage to \"proc-receive\" hook, and these functions die immediately\nwhen pipe is broken. Using \"gently\" forms for these functions will get\nmore predicable output.\n\nAdd more \"--die-*\" options to test helper to test different stages of\nthe protocol between \"receive-pack\" and \"proc-receive\" hook.\n\nReported-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nSuggested-by: Jeff King <peff@peff.net>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c                       |  87 +++++++---\n t/helper/test-proc-receive.c                 |  42 +++--\n t/t5411/test-0013-bad-protocol.sh            | 138 +++++++++++++--\n t/t5411/test-0014-bad-protocol--porcelain.sh | 173 ++++++++++++++++++-\n 4 files changed, 392 insertions(+), 48 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex bb9909c52e..2bd736525f 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -977,15 +977,25 @@ static int read_proc_receive_report(struct packet_reader *reader,\n \tint new_report = 0;\n \tint code = 0;\n \tint once = 0;\n+\tint response = 0;\n \n \tfor (;;) {\n \t\tstruct object_id old_oid, new_oid;\n \t\tconst char *head;\n \t\tconst char *refname;\n \t\tchar *p;\n-\n-\t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n+\t\tenum packet_read_status status;\n+\n+\t\tstatus = packet_reader_read(reader);\n+\t\tif (status != PACKET_READ_NORMAL) {\n+\t\t\t/* Check whether proc-receive exited abnormally */\n+\t\t\tif (status == PACKET_READ_EOF && !response) {\n+\t\t\t\tstrbuf_addstr(errmsg, \"proc-receive exited abnormally\");\n+\t\t\t\treturn -1;\n+\t\t\t}\n \t\t\tbreak;\n+\t\t}\n+\t\tresponse++;\n \n \t\thead = reader->line;\n \t\tp = strchr(head, ' ');\n@@ -1145,28 +1155,41 @@ static int run_proc_receive_hook(struct command *commands,\n \tif (use_push_options)\n \t\tstrbuf_addstr(&cap, \" push-options\");\n \tif (cap.len) {\n-\t\tpacket_write_fmt(proc.in, \"version=1%c%s\\n\", '\\0', cap.buf + 1);\n+\t\tcode = packet_write_fmt_gently(proc.in, \"version=1%c%s\\n\", '\\0', cap.buf + 1);\n \t\tstrbuf_release(&cap);\n \t} else {\n-\t\tpacket_write_fmt(proc.in, \"version=1\\n\");\n+\t\tcode = packet_write_fmt_gently(proc.in, \"version=1\\n\");\n \t}\n-\tpacket_flush(proc.in);\n+\tif (!code)\n+\t\tcode = packet_flush_gently(proc.in);\n \n-\tfor (;;) {\n-\t\tint linelen;\n+\tif (!code)\n+\t\tfor (;;) {\n+\t\t\tint linelen;\n+\t\t\tenum packet_read_status status;\n \n-\t\tif (packet_reader_read(&reader) != PACKET_READ_NORMAL)\n-\t\t\tbreak;\n+\t\t\tstatus = packet_reader_read(&reader);\n+\t\t\tif (status != PACKET_READ_NORMAL) {\n+\t\t\t\t/* Check whether proc-receive exited abnormally */\n+\t\t\t\tif (status == PACKET_READ_EOF)\n+\t\t\t\t\tcode = -1;\n+\t\t\t\tbreak;\n+\t\t\t}\n \n-\t\tif (reader.pktlen > 8 && starts_with(reader.line, \"version=\")) {\n-\t\t\tversion = atoi(reader.line + 8);\n-\t\t\tlinelen = strlen(reader.line);\n-\t\t\tif (linelen < reader.pktlen) {\n-\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n-\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n-\t\t\t\t\thook_use_push_options = 1;\n+\t\t\tif (reader.pktlen > 8 && starts_with(reader.line, \"version=\")) {\n+\t\t\t\tversion = atoi(reader.line + 8);\n+\t\t\t\tlinelen = strlen(reader.line);\n+\t\t\t\tif (linelen < reader.pktlen) {\n+\t\t\t\t\tconst char *feature_list = reader.line + linelen + 1;\n+\t\t\t\t\tif (parse_feature_request(feature_list, \"push-options\"))\n+\t\t\t\t\t\thook_use_push_options = 1;\n+\t\t\t\t}\n \t\t\t}\n \t\t}\n+\n+\tif (code) {\n+\t\tstrbuf_addstr(&errmsg, \"fail to negotiate version with proc-receive hook\");\n+\t\tgoto cleanup;\n \t}\n \n \tif (version != 1) {\n@@ -1180,20 +1203,36 @@ static int run_proc_receive_hook(struct command *commands,\n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n \t\tif (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)\n \t\t\tcontinue;\n-\t\tpacket_write_fmt(proc.in, \"%s %s %s\",\n-\t\t\t\t oid_to_hex(&cmd->old_oid),\n-\t\t\t\t oid_to_hex(&cmd->new_oid),\n-\t\t\t\t cmd->ref_name);\n+\t\tcode = packet_write_fmt_gently(proc.in, \"%s %s %s\",\n+\t\t\t\t\t       oid_to_hex(&cmd->old_oid),\n+\t\t\t\t\t       oid_to_hex(&cmd->new_oid),\n+\t\t\t\t\t       cmd->ref_name);\n+\t\tif (code)\n+\t\t\tbreak;\n+\t}\n+\tif (!code)\n+\t\tcode = packet_flush_gently(proc.in);\n+\tif (code) {\n+\t\tstrbuf_addstr(&errmsg, \"fail to write commands to proc-receive hook\");\n+\t\tgoto cleanup;\n \t}\n-\tpacket_flush(proc.in);\n \n \t/* Send push options */\n \tif (hook_use_push_options) {\n \t\tstruct string_list_item *item;\n \n-\t\tfor_each_string_list_item(item, push_options)\n-\t\t\tpacket_write_fmt(proc.in, \"%s\", item->string);\n-\t\tpacket_flush(proc.in);\n+\t\tfor_each_string_list_item(item, push_options) {\n+\t\t\tcode = packet_write_fmt_gently(proc.in, \"%s\", item->string);\n+\t\t\tif (code)\n+\t\t\t\tbreak;\n+\t\t}\n+\t\tif (!code)\n+\t\t\tcode = packet_flush_gently(proc.in);\n+\t\tif (code) {\n+\t\t\tstrbuf_addstr(&errmsg,\n+\t\t\t\t      \"fail to write push-options to proc-receive hook\");\n+\t\t\tgoto cleanup;\n+\t\t}\n \t}\n \n \t/* Read result from proc-receive */\ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nindex 42164d9898..6652cedcee 100644\n--- a/t/helper/test-proc-receive.c\n+++ b/t/helper/test-proc-receive.c\n@@ -10,8 +10,11 @@ static const char *proc_receive_usage[] = {\n \tNULL\n };\n \n-static int die_version;\n-static int die_readline;\n+static int die_read_version;\n+static int die_write_version;\n+static int die_read_commands;\n+static int die_read_push_options;\n+static int die_write_report;\n static int no_push_options;\n static int use_atomic;\n static int use_push_options;\n@@ -33,6 +36,9 @@ struct command {\n static void proc_receive_verison(struct packet_reader *reader) {\n \tint server_version = 0;\n \n+\tif (die_read_version)\n+\t\tdie(\"die with the --die-read-version option\");\n+\n \tfor (;;) {\n \t\tint linelen;\n \n@@ -52,9 +58,12 @@ static void proc_receive_verison(struct packet_reader *reader) {\n \t\t}\n \t}\n \n-\tif (server_version != 1 || die_version)\n+\tif (server_version != 1)\n \t\tdie(\"bad protocol version: %d\", server_version);\n \n+\tif (die_write_version)\n+\t\tdie(\"die with the --die-write-version option\");\n+\n \tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n \t\t\t version, '\\0',\n \t\t\t use_push_options && !no_push_options ? \"push-options\": \"\");\n@@ -75,11 +84,13 @@ static void proc_receive_read_commands(struct packet_reader *reader,\n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n \t\t\tbreak;\n \n+\t\tif (die_read_commands)\n+\t\t\tdie(\"die with the --die-read-commands option\");\n+\n \t\tif (parse_oid_hex(reader->line, &old_oid, &p) ||\n \t\t    *p++ != ' ' ||\n \t\t    parse_oid_hex(p, &new_oid, &p) ||\n-\t\t    *p++ != ' ' ||\n-\t\t    die_readline)\n+\t\t    *p++ != ' ')\n \t\t\tdie(\"protocol error: expected 'old new ref', got '%s'\",\n \t\t\t    reader->line);\n \t\trefname = p;\n@@ -99,6 +110,9 @@ static void proc_receive_read_push_options(struct packet_reader *reader,\n \tif (no_push_options || !use_push_options)\n \t       return;\n \n+\tif (die_read_push_options)\n+\t\tdie(\"die with the --die-read-push-options option\");\n+\n \twhile (1) {\n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n \t\t\tbreak;\n@@ -117,10 +131,16 @@ int cmd__proc_receive(int argc, const char **argv)\n \tstruct option options[] = {\n \t\tOPT_BOOL(0, \"no-push-options\", &no_push_options,\n \t\t\t \"disable push options\"),\n-\t\tOPT_BOOL(0, \"die-version\", &die_version,\n-\t\t\t \"die during version negotiation\"),\n-\t\tOPT_BOOL(0, \"die-readline\", &die_readline,\n-\t\t\t \"die when readline\"),\n+\t\tOPT_BOOL(0, \"die-read-version\", &die_read_version,\n+\t\t\t \"die when reading version\"),\n+\t\tOPT_BOOL(0, \"die-write-version\", &die_write_version,\n+\t\t\t \"die when writing version\"),\n+\t\tOPT_BOOL(0, \"die-read-commands\", &die_read_commands,\n+\t\t\t \"die when reading commands\"),\n+\t\tOPT_BOOL(0, \"die-read-push-options\", &die_read_push_options,\n+\t\t\t \"die when reading push-options\"),\n+\t\tOPT_BOOL(0, \"die-write-report\", &die_write_report,\n+\t\t\t \"die when writing report\"),\n \t\tOPT_STRING_LIST('r', \"return\", &returns, \"old/new/ref/status/msg\",\n \t\t\t\t\"return of results\"),\n \t\tOPT__VERBOSE(&verbose, \"be verbose\"),\n@@ -136,7 +156,7 @@ int cmd__proc_receive(int argc, const char **argv)\n \t\tusage_msg_opt(\"Too many arguments.\", proc_receive_usage, options);\n \tpacket_reader_init(&reader, 0, NULL, 0,\n \t\t\t   PACKET_READ_CHOMP_NEWLINE |\n-\t\t\t   PACKET_READ_DIE_ON_ERR_PACKET);\n+\t\t\t   PACKET_READ_GENTLE_ON_EOF);\n \n \tsigchain_push(SIGPIPE, SIG_IGN);\n \tproc_receive_verison(&reader);\n@@ -166,6 +186,8 @@ int cmd__proc_receive(int argc, const char **argv)\n \t\t\t\tfprintf(stderr, \"proc-receive> %s\\n\", item->string);\n \t}\n \n+\tif (die_write_report)\n+\t\tdie(\"die with the --die-write-report option\");\n \tif (returns.nr)\n \t\tfor_each_string_list_item(item, &returns)\n \t\t\tpacket_write_fmt(1, \"%s\\n\", item->string);\ndiff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh\nindex 854c3e884a..b9be12be77 100644\n--- a/t/t5411/test-0013-bad-protocol.sh\n+++ b/t/t5411/test-0013-bad-protocol.sh\n@@ -16,7 +16,8 @@ test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL)\" '\n \n \t# Check status report for git-push\n \tsed -n \\\n-\t\t-e \"/^To / { p; n; p; }\" \\\n+\t\t-e \"/^To / { p; }\" \\\n+\t\t-e \"/^ ! / { p; }\" \\\n \t\t<actual >actual-report &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n@@ -41,32 +42,98 @@ test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL)\" '\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"setup proc-receive hook (hook --die-version, $PROTOCOL)\" '\n+test_expect_success \"setup proc-receive hook (hook --die-read-version, $PROTOCOL)\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n-\ttest-tool proc-receive -v --die-version\n+\ttest-tool proc-receive -v --die-read-version\n \tEOF\n '\n \n # Refs of upstream : main(A)\n # Refs of workbench: main(A)  tags/v123\n # git push         :                       refs/for/main/topic(A)\n-test_expect_success \"proc-receive: bad protocol (hook --die-version, $PROTOCOL)\" '\n+test_expect_success \"proc-receive: bad protocol (hook --die-read-version, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/main/topic \\\n \t\t>out 2>&1 &&\n+\tfilter_out_user_friendly_and_stable_output \\\n+\t\t-e \"/^To / { p; }\" \\\n+\t\t-e \"/^ ! / { p; }\" \\\n+\t\t<out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgrep \"remote: fatal: die with the --die-read-version option\" out &&\n+\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/main\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-write-version, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-write-version\n+\tEOF\n+'\n \n+# Refs of upstream : main(A)\n+# Refs of workbench: main(A)  tags/v123\n+# git push         :                       refs/for/main/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-write-version, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/main/topic \\\n+\t\t>out 2>&1 &&\n+\tfilter_out_user_friendly_and_stable_output \\\n+\t\t-e \"/^To / { p; }\" \\\n+\t\t-e \"/^ ! / { p; }\" \\\n+\t\t<out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgrep \"remote: fatal: die with the --die-write-version option\" out &&\n+\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/main\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-read-commands, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-read-commands\n+\tEOF\n+'\n+\n+# Refs of upstream : main(A)\n+# Refs of workbench: main(A)  tags/v123\n+# git push         :                       refs/for/main/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-read-commands, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/main/topic \\\n+\t\t>out 2>&1 &&\n+\tfilter_out_user_friendly_and_stable_output \\\n+\t\t-e \"/^To / { p; }\" \\\n+\t\t-e \"/^ ! / { p; }\" \\\n+\t\t<out >actual &&\n \tcat >expect <<-EOF &&\n-\tremote: # pre-receive hook\n-\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n-\tremote: # proc-receive hook\n-\tremote: fatal: bad protocol version: 1\n-\tremote: error: proc-receive version \"0\" is not supported\n \tTo <URL/of/upstream.git>\n \t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n \tEOF\n \ttest_cmp expect actual &&\n+\tgrep \"remote: fatal: die with the --die-read-commands option\" out &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -76,23 +143,65 @@ test_expect_success \"proc-receive: bad protocol (hook --die-version, $PROTOCOL)\"\n \ttest_cmp expect actual\n '\n \n-test_expect_success \"setup proc-receive hook (hook --die-readline, $PROTOCOL)\" '\n+test_expect_success \"setup proc-receive hook (hook --die-read-push-options, $PROTOCOL)\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n-\ttest-tool proc-receive -v --die-readline\n+\ttest-tool proc-receive -v --die-read-push-options\n \tEOF\n '\n \n # Refs of upstream : main(A)\n # Refs of workbench: main(A)  tags/v123\n # git push         :                       refs/for/main/topic(A)\n-test_expect_success \"proc-receive: bad protocol (hook --die-readline, $PROTOCOL)\" '\n+test_expect_success \"proc-receive: bad protocol (hook --die-read-push-options, $PROTOCOL)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true &&\n \ttest_must_fail git -C workbench push origin \\\n+\t\t-o reviewers=user1,user2 \\\n \t\tHEAD:refs/for/main/topic \\\n \t\t>out 2>&1 &&\n+\tfilter_out_user_friendly_and_stable_output \\\n+\t\t-e \"/^To / { p; }\" \\\n+\t\t-e \"/^ ! / { p; }\" \\\n+\t\t<out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgrep \"remote: fatal: die with the --die-read-push-options option\" out &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/main\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-write-report, $PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-write-report\n+\tEOF\n+'\n \n-\tgrep \"remote: fatal: protocol error: expected \\\"old new ref\\\", got \\\"<ZERO-OID> <COMMIT-A> refs/for/main/topic\\\"\" actual &&\n+# Refs of upstream : main(A)\n+# Refs of workbench: main(A)  tags/v123\n+# git push         :                       refs/for/main/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-write-report, $PROTOCOL)\" '\n+\ttest_must_fail git -C workbench push origin \\\n+\t\tHEAD:refs/for/main/topic \\\n+\t\t>out 2>&1 &&\n+\tfilter_out_user_friendly_and_stable_output \\\n+\t\t-e \"/^To / { p; }\" \\\n+\t\t-e \"/^ ! / { p; }\" \\\n+\t\t<out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgrep \"remote: fatal: die with the --die-write-report option\" out &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -130,6 +239,7 @@ test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL)\" '\n \t ! [remote rejected] HEAD -> refs/for/main/topic (proc-receive failed to report status)\n \tEOF\n \ttest_cmp expect actual &&\n+\n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n@@ -173,6 +283,7 @@ test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL)\" '\n \t ! [remote rejected] HEAD -> refs/for/main/topic (proc-receive failed to report status)\n \tEOF\n \ttest_cmp expect actual &&\n+\n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n@@ -208,6 +319,7 @@ test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL)\" '\n \t ! [remote rejected] HEAD -> refs/for/main/topic (proc-receive failed to report status)\n \tEOF\n \ttest_cmp expect actual &&\n+\n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\ndiff --git a/t/t5411/test-0014-bad-protocol--porcelain.sh b/t/t5411/test-0014-bad-protocol--porcelain.sh\nindex 88c56311da..fdb4569109 100644\n--- a/t/t5411/test-0014-bad-protocol--porcelain.sh\n+++ b/t/t5411/test-0014-bad-protocol--porcelain.sh\n@@ -42,6 +42,175 @@ test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL/porc\n \ttest_cmp expect actual\n '\n \n+test_expect_success \"setup proc-receive hook (hook --die-read-version, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-read-version\n+\tEOF\n+'\n+\n+# Refs of upstream : main(A)\n+# Refs of workbench: main(A)  tags/v123\n+# git push         :                       refs/for/main/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-read-version, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/main/topic \\\n+\t\t>out 2>&1 &&\n+\tfilter_out_user_friendly_and_stable_output \\\n+\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n+\t\t<out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgrep \"remote: fatal: die with the --die-read-version option\" out &&\n+\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/main\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-write-version, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-write-version\n+\tEOF\n+'\n+\n+# Refs of upstream : main(A)\n+# Refs of workbench: main(A)  tags/v123\n+# git push         :                       refs/for/main/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-write-version, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/main/topic \\\n+\t\t>out 2>&1 &&\n+\tfilter_out_user_friendly_and_stable_output \\\n+\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n+\t\t<out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgrep \"remote: fatal: die with the --die-write-version option\" out &&\n+\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/main\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-read-commands, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-read-commands\n+\tEOF\n+'\n+\n+# Refs of upstream : main(A)\n+# Refs of workbench: main(A)  tags/v123\n+# git push         :                       refs/for/main/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-read-commands, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/main/topic \\\n+\t\t>out 2>&1 &&\n+\tfilter_out_user_friendly_and_stable_output \\\n+\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n+\t\t<out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgrep \"remote: fatal: die with the --die-read-commands option\" out &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/main\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-read-push-options, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-read-push-options\n+\tEOF\n+'\n+\n+# Refs of upstream : main(A)\n+# Refs of workbench: main(A)  tags/v123\n+# git push         :                       refs/for/main/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-read-push-options, $PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" config receive.advertisePushOptions true &&\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\t-o reviewers=user1,user2 \\\n+\t\tHEAD:refs/for/main/topic \\\n+\t\t>out 2>&1 &&\n+\tfilter_out_user_friendly_and_stable_output \\\n+\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n+\t\t<out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgrep \"remote: fatal: die with the --die-read-push-options option\" out &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/main\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"setup proc-receive hook (hook --die-write-report, $PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v --die-write-report\n+\tEOF\n+'\n+\n+# Refs of upstream : main(A)\n+# Refs of workbench: main(A)  tags/v123\n+# git push         :                       refs/for/main/topic(A)\n+test_expect_success \"proc-receive: bad protocol (hook --die-write-report, $PROTOCOL/porcelain)\" '\n+\ttest_must_fail git -C workbench push --porcelain origin \\\n+\t\tHEAD:refs/for/main/topic \\\n+\t\t>out 2>&1 &&\n+\tfilter_out_user_friendly_and_stable_output \\\n+\t\t-e \"/^To / { p; n; p; n; p; }\" \\\n+\t\t<out >actual &&\n+\tcat >expect <<-EOF &&\n+\tTo <URL/of/upstream.git>\n+\t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgrep \"remote: fatal: die with the --die-write-report option\" out &&\n+\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/main\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success \"setup proc-receive hook (no report, $PROTOCOL/porcelain)\" '\n \twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n \tprintf >&2 \"# proc-receive hook\\n\"\n@@ -71,6 +240,7 @@ test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL/porcelain)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n+\n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n@@ -84,7 +254,6 @@ test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL/porcelain)\n # Refs of workbench: main(A)  tags/v123\n test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n \tgit -C \"$upstream\" update-ref -d refs/heads/next\n-\n '\n \n test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL/porcelain)\" '\n@@ -115,6 +284,7 @@ test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n+\n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n@@ -151,6 +321,7 @@ test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL/porce\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n+\n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n-- \n2.29.0.dirty\n\n"},{"id":"409604","messageId":"20201111113202.24911-4-zhiyou.jx@alibaba-inc.com","threadId":"52936","inReplyTo":"CANYiYbH-x6khgTkkFV29+7AjghOZmG69_6-sQcm2489WMHOWAA@mail.gmail.com","subject":"[PATCH v4 3/3] receive-pack: use default version 0 for proc-receive","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2020-11-11T11:32:02Z","receivedAt":"2020-11-11T11:32:35Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"In the verison negotiation phase between \"receive-pack\" and\n\"proc-receive\", \"proc-receive\" can send an empty flush-pkt to end the\nnegotiation and use default version 0. Capabilities (such as\n\"push-options\") are not supported in version 0.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n builtin/receive-pack.c                       |  7 ++-\n t/helper/test-proc-receive.c                 | 16 +++--\n t/t5411/test-0026-push-options.sh            | 60 +++++++++++++++++++\n t/t5411/test-0027-push-options--porcelain.sh | 62 ++++++++++++++++++++\n 4 files changed, 138 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 2bd736525f..f1f0f7bef6 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1192,7 +1192,12 @@ static int run_proc_receive_hook(struct command *commands,\n \t\tgoto cleanup;\n \t}\n \n-\tif (version != 1) {\n+\tswitch (version) {\n+\tcase 0:\n+\t\t/* fallthrough */\n+\tcase 1:\n+\t\tbreak;\n+\tdefault:\n \t\tstrbuf_addf(&errmsg, \"proc-receive version '%d' is not supported\",\n \t\t\t    version);\n \t\tcode = -1;\ndiff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c\nindex 6652cedcee..cc08506cf0 100644\n--- a/t/helper/test-proc-receive.c\n+++ b/t/helper/test-proc-receive.c\n@@ -45,8 +45,14 @@ static void proc_receive_verison(struct packet_reader *reader) {\n \t\tif (packet_reader_read(reader) != PACKET_READ_NORMAL)\n \t\t\tbreak;\n \n+\t\t/* Ignore version negotiation for version 0 */\n+\t\tif (version == 0)\n+\t\t\tcontinue;\n+\n \t\tif (reader->pktlen > 8 && starts_with(reader->line, \"version=\")) {\n \t\t\tserver_version = atoi(reader->line+8);\n+\t\t\tif (server_version != 1)\n+\t\t\t\tdie(\"bad protocol version: %d\", server_version);\n \t\t\tlinelen = strlen(reader->line);\n \t\t\tif (linelen < reader->pktlen) {\n \t\t\t\tconst char *feature_list = reader->line + linelen + 1;\n@@ -58,15 +64,13 @@ static void proc_receive_verison(struct packet_reader *reader) {\n \t\t}\n \t}\n \n-\tif (server_version != 1)\n-\t\tdie(\"bad protocol version: %d\", server_version);\n-\n \tif (die_write_version)\n \t\tdie(\"die with the --die-write-version option\");\n \n-\tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n-\t\t\t version, '\\0',\n-\t\t\t use_push_options && !no_push_options ? \"push-options\": \"\");\n+\tif (version != 0)\n+\t\tpacket_write_fmt(1, \"version=%d%c%s\\n\",\n+\t\t\t\t version, '\\0',\n+\t\t\t\t use_push_options && !no_push_options ? \"push-options\": \"\");\n \tpacket_flush(1);\n }\n \ndiff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh\nindex d414be87d0..e88edb16a4 100644\n--- a/t/t5411/test-0026-push-options.sh\n+++ b/t/t5411/test-0026-push-options.sh\n@@ -32,6 +32,66 @@ test_expect_success \"enable push options ($PROTOCOL)\" '\n \tgit -C \"$upstream\" config receive.advertisePushOptions true\n '\n \n+test_expect_success \"setup version=0 for proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t--version 0 \\\n+\t\t-r \"ok refs/for/main/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : main(A)\n+# Refs of workbench: main(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/main/topic\n+test_expect_success \"proc-receive: ignore push-options for version 0 ($PROTOCOL)\" '\n+\tgit -C workbench push \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/main/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n+\tremote: proc-receive> ok refs/for/main/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n+\tTo <URL/of/upstream.git>\n+\t * [new branch] HEAD -> next\n+\t * [new reference] HEAD -> refs/for/main/topic\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/main\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"restore proc-receive hook ($PROTOCOL)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/main/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : main(A)             next(A)\n+# Refs of workbench: main(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n # Refs of upstream : main(A)\n # Refs of workbench: main(A)  tags/v123\n # git push -o ...  :                       next(A)  refs/for/main/topic\ndiff --git a/t/t5411/test-0027-push-options--porcelain.sh b/t/t5411/test-0027-push-options--porcelain.sh\nindex d5d0dcb172..3a6561b5ea 100644\n--- a/t/t5411/test-0027-push-options--porcelain.sh\n+++ b/t/t5411/test-0027-push-options--porcelain.sh\n@@ -33,6 +33,68 @@ test_expect_success \"enable push options ($PROTOCOL/porcelain)\" '\n \tgit -C \"$upstream\" config receive.advertisePushOptions true\n '\n \n+test_expect_success \"setup version=0 for proc-receive hook ($PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t--version 0 \\\n+\t\t-r \"ok refs/for/main/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : main(A)\n+# Refs of workbench: main(A)  tags/v123\n+# git push -o ...  :                       next(A)  refs/for/main/topic\n+test_expect_success \"proc-receive: ignore push-options for version 0 ($PROTOCOL/porcelain)\" '\n+\tgit -C workbench push \\\n+\t\t--porcelain \\\n+\t\t--atomic \\\n+\t\t-o issue=123 \\\n+\t\t-o reviewer=user1 \\\n+\t\torigin \\\n+\t\tHEAD:refs/heads/next \\\n+\t\tHEAD:refs/for/main/topic \\\n+\t\t>out 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\tremote: # pre-receive hook\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n+\tremote: # proc-receive hook\n+\tremote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n+\tremote: proc-receive> ok refs/for/main/topic\n+\tremote: # post-receive hook\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n+\tremote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n+\tTo <URL/of/upstream.git>\n+\t*    HEAD:refs/heads/next    [new branch]\n+\t*    HEAD:refs/for/main/topic    [new reference]\n+\tDone\n+\tEOF\n+\ttest_cmp expect actual &&\n+\tgit -C \"$upstream\" show-ref >out &&\n+\tmake_user_friendly_and_stable_output <out >actual &&\n+\tcat >expect <<-EOF &&\n+\t<COMMIT-A> refs/heads/main\n+\t<COMMIT-A> refs/heads/next\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"restore proc-receive hook ($PROTOCOL/porcelain)\" '\n+\twrite_script \"$upstream/hooks/proc-receive\" <<-EOF\n+\tprintf >&2 \"# proc-receive hook\\n\"\n+\ttest-tool proc-receive -v \\\n+\t\t-r \"ok refs/for/main/topic\"\n+\tEOF\n+'\n+\n+# Refs of upstream : main(A)             next(A)\n+# Refs of workbench: main(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n+\n # Refs of upstream : main(A)\n # Refs of workbench: main(A)  tags/v123\n # git push -o ...  :                       next(A)  refs/for/main/topic\n-- \n2.29.0.dirty\n\n"},{"id":"414559","messageId":"20210117222151.GY8396@szeder.dev","threadId":"52936","inReplyTo":"20200827154551.5966-4-worldhello.net@gmail.com","subject":"Re: [PATCH v19 03/10] receive-pack: add new proc-receive hook","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2021-01-17T22:21:51Z","receivedAt":"2021-01-17T22:23:17Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"\nThis patch added a whole lot of test cases, and two of them '86 -\nproc-receive: not support push options (builtin protocol)' and '95 -\nproc-receive: not support push options (builtin protocol/porcelain)'\nare prone to rare failures.\n\nOn Thu, Aug 27, 2020 at 11:45:44AM -0400, Jiang Xin wrote:\n> diff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh\n> new file mode 100644\n> index 0000000000..d0c4da8b23\n> --- /dev/null\n> +++ b/t/t5411/test-0026-push-options.sh\n\n> +# Refs of upstream : master(A)\n> +# Refs of workbench: master(A)  tags/v123\n> +# git push -o ...  :                       refs/for/master/topic\n> +test_expect_success \"proc-receive: not support push options ($PROTOCOL)\" '\n> +\ttest_must_fail git -C workbench push \\\n> +\t\t-o issue=123 \\\n> +\t\t-o reviewer=user1 \\\n> +\t\torigin \\\n> +\t\tHEAD:refs/for/master/topic \\\n> +\t\t>out 2>&1 &&\n\nThree relevant things are happening here:\n\n  - 'git push' is executed with its standard output and error\n    redirected to the file 'out'.\n\n  - 'git push' executes 'git receive-pack' internally, which inherits\n    the open file descriptors, so its output and error goes into that\n    same 'out' file.\n\n  - 'git push' is expected to fail when it finds out that the other\n    side doesn't support push options, but it does so with a simple\n    die() right away, without waiting for its child 'git receive-pack'\n    process to finish.\n\n> +\tmake_user_friendly_and_stable_output <out >actual &&\n> +\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n> +\t\tactual &&\n> +\tgit -C \"$upstream\" show-ref >out &&\n\nHere the shell opens and truncates the file 'out' to write 'git\nshow-ref's output, i.e. it is still the same 'out' file that was used\nearlier.\n\nConsequently, it is possible that 'git receive-pack' is still running,\nits open file descriptors to 'out' are still valid, and its \"fatal:\nthe remote end hung up unexpectedly\" error message about the suddenly\ndisappeared 'git push' can partially overwrite the output from 'git\nshow-ref'.\n\n> +\tmake_user_friendly_and_stable_output <out >actual &&\n> +\tcat >expect <<-EOF &&\n> +\t<COMMIT-A> refs/heads/master\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n\nThe supposedly stable output can then unexpectedly include that error\nmessage, and the test fails with something like this:\n\n  + test_cmp expect actual\n  --- expect      2021-01-17 21:55:23.430750004 +0000\n  +++ actual      2021-01-17 21:55:23.430750004 +0000\n  @@ -1 +1 @@\n  -<COMMIT-A> refs/heads/main\n  +<COMMIT-A> refs/heads/maifatal: the remote end hung up unexpectedly\n  error: last command exited with $?=1\n  not ok 86 - proc-receive: not support push options (builtin protocol)\n\n> diff --git a/t/t5411/test-0027-push-options--porcelain.sh b/t/t5411/test-0027-push-options--porcelain.sh\n> new file mode 100644\n> index 0000000000..c89a1e7c57\n> --- /dev/null\n> +++ b/t/t5411/test-0027-push-options--porcelain.sh\n\n> +test_expect_success \"proc-receive: not support push options ($PROTOCOL/porcelain)\" '\n> +\ttest_must_fail git -C workbench push \\\n> +\t\t--porcelain \\\n> +\t\t-o issue=123 \\\n> +\t\t-o reviewer=user1 \\\n> +\t\torigin \\\n> +\t\tHEAD:refs/for/master/topic \\\n> +\t\t>out 2>&1 &&\n> +\tmake_user_friendly_and_stable_output <out >actual &&\n> +\ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n> +\t\tactual &&\n> +\tgit -C \"$upstream\" show-ref >out &&\n> +\tmake_user_friendly_and_stable_output <out >actual &&\n> +\tcat >expect <<-EOF &&\n> +\t<COMMIT-A> refs/heads/master\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n\nThis test is almost identical to the one above, and the same sequence\nof events leads to a similar failure.\n\n\nI saw these tests fail in Travis CI's s390x environment a couple of\ntimes, and, alas, that is the only environment where I was able to\nreproduce the failure with '--stress' with an unmodified Git.\n\nThe diff below adds a couple of strategically-placed delays to\nreliably demonstrate these failures:\n\n  ---  >8  ---\n\ndiff --git a/pkt-line.c b/pkt-line.c\nindex d633005ef7..3b26631948 100644\n--- a/pkt-line.c\n+++ b/pkt-line.c\n@@ -307,6 +307,7 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,\n \t\tif (options & PACKET_READ_GENTLE_ON_EOF)\n \t\t\treturn -1;\n \n+\t\tsleep(1);\n \t\tdie(_(\"the remote end hung up unexpectedly\"));\n \t}\n \ndiff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh\nindex e88edb16a4..a1e896f404 100644\n--- a/t/t5411/test-0026-push-options.sh\n+++ b/t/t5411/test-0026-push-options.sh\n@@ -21,6 +21,7 @@ test_expect_success \"proc-receive: not support push options ($PROTOCOL)\" '\n \ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n \t\tactual &&\n \tgit -C \"$upstream\" show-ref >out &&\n+\tsleep 2 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n \t<COMMIT-A> refs/heads/main\ndiff --git a/t/t5411/test-0027-push-options--porcelain.sh b/t/t5411/test-0027-push-options--porcelain.sh\nindex 3a6561b5ea..f97cef440f 100644\n--- a/t/t5411/test-0027-push-options--porcelain.sh\n+++ b/t/t5411/test-0027-push-options--porcelain.sh\n@@ -22,6 +22,7 @@ test_expect_success \"proc-receive: not support push options ($PROTOCOL/porcelain\n \ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n \t\tactual &&\n \tgit -C \"$upstream\" show-ref >out &&\n+\tsleep 2 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n \tcat >expect <<-EOF &&\n \t<COMMIT-A> refs/heads/main\n\n  ---  8<  ---\n\nI think these are the only two tests that can cause this racy\nbehavior: by instrumenting finish_command() I found that in all other\ntests where 'git push' is expected to fail it errors out gracefully\nand waits for its 'git receive-pack' child process.\n\n\n"},{"id":"414587","messageId":"CANYiYbE358amO95JStnspS-N-9WMYENiYspdKtsGsUc7Hv5jWw@mail.gmail.com","threadId":"52936","inReplyTo":"20210117222151.GY8396@szeder.dev","subject":"Re: [PATCH v19 03/10] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2021-01-18T08:24:11Z","receivedAt":"2021-01-18T08:25:06Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"SZEDER Gábor <szeder.dev@gmail.com> 于2021年1月18日周一 上午6:21写道：\n>\n>\n> This patch added a whole lot of test cases, and two of them '86 -\n> proc-receive: not support push options (builtin protocol)' and '95 -\n> proc-receive: not support push options (builtin protocol/porcelain)'\n> are prone to rare failures.\n>\n> On Thu, Aug 27, 2020 at 11:45:44AM -0400, Jiang Xin wrote:\n> > diff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh\n> > new file mode 100644\n> > index 0000000000..d0c4da8b23\n> > --- /dev/null\n> > +++ b/t/t5411/test-0026-push-options.sh\n>\n> > +# Refs of upstream : master(A)\n> > +# Refs of workbench: master(A)  tags/v123\n> > +# git push -o ...  :                       refs/for/master/topic\n> > +test_expect_success \"proc-receive: not support push options ($PROTOCOL)\" '\n> > +     test_must_fail git -C workbench push \\\n> > +             -o issue=123 \\\n> > +             -o reviewer=user1 \\\n> > +             origin \\\n> > +             HEAD:refs/for/master/topic \\\n> > +             >out 2>&1 &&\n>\n> Three relevant things are happening here:\n>\n>   - 'git push' is executed with its standard output and error\n>     redirected to the file 'out'.\n>\n>   - 'git push' executes 'git receive-pack' internally, which inherits\n>     the open file descriptors, so its output and error goes into that\n>     same 'out' file.\n>\n>   - 'git push' is expected to fail when it finds out that the other\n>     side doesn't support push options, but it does so with a simple\n>     die() right away, without waiting for its child 'git receive-pack'\n>     process to finish.\n>\n> > +     make_user_friendly_and_stable_output <out >actual &&\n> > +     test_i18ngrep \"fatal: the receiving end does not support push options\" \\\n> > +             actual &&\n> > +     git -C \"$upstream\" show-ref >out &&\n>\n> Here the shell opens and truncates the file 'out' to write 'git\n> show-ref's output, i.e. it is still the same 'out' file that was used\n> earlier.\n>\n> Consequently, it is possible that 'git receive-pack' is still running,\n> its open file descriptors to 'out' are still valid, and its \"fatal:\n> the remote end hung up unexpectedly\" error message about the suddenly\n> disappeared 'git push' can partially overwrite the output from 'git\n> show-ref'.\n\nThank you for providing the accurate analysis of the root cause of this problem.\n\n> I saw these tests fail in Travis CI's s390x environment a couple of\n> times, and, alas, that is the only environment where I was able to\n> reproduce the failure with '--stress' with an unmodified Git.\n>\n> The diff below adds a couple of strategically-placed delays to\n> reliably demonstrate these failures:\n\nTo solve this problem, instead of adding delays, we can simply remove\nthe \"out\" file before creating a new one, thus the two \"out\" files\nhave two different file descriptors, and the process in background\nwon't break the new \"out\" file.\n\n>\n>   ---  >8  ---\n>\n> diff --git a/pkt-line.c b/pkt-line.c\n> index d633005ef7..3b26631948 100644\n> --- a/pkt-line.c\n> +++ b/pkt-line.c\n> @@ -307,6 +307,7 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,\n>                 if (options & PACKET_READ_GENTLE_ON_EOF)\n>                         return -1;\n>\n> +               sleep(1);\n>                 die(_(\"the remote end hung up unexpectedly\"));\n>         }\n>\n> diff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh\n> index e88edb16a4..a1e896f404 100644\n> --- a/t/t5411/test-0026-push-options.sh\n> +++ b/t/t5411/test-0026-push-options.sh\n> @@ -21,6 +21,7 @@ test_expect_success \"proc-receive: not support push options ($PROTOCOL)\" '\n>         test_i18ngrep \"fatal: the receiving end does not support push options\" \\\n>                 actual &&\n>         git -C \"$upstream\" show-ref >out &&\n> +       sleep 2 &&\n>         make_user_friendly_and_stable_output <out >actual &&\n>         cat >expect <<-EOF &&\n>         <COMMIT-A> refs/heads/main\n> diff --git a/t/t5411/test-0027-push-options--porcelain.sh b/t/t5411/test-0027-push-options--porcelain.sh\n> index 3a6561b5ea..f97cef440f 100644\n> --- a/t/t5411/test-0027-push-options--porcelain.sh\n> +++ b/t/t5411/test-0027-push-options--porcelain.sh\n> @@ -22,6 +22,7 @@ test_expect_success \"proc-receive: not support push options ($PROTOCOL/porcelain\n>         test_i18ngrep \"fatal: the receiving end does not support push options\" \\\n>                 actual &&\n>         git -C \"$upstream\" show-ref >out &&\n> +       sleep 2 &&\n>         make_user_friendly_and_stable_output <out >actual &&\n>         cat >expect <<-EOF &&\n>         <COMMIT-A> refs/heads/main\n>\n>   ---  8<  ---\n>\n> I think these are the only two tests that can cause this racy\n> behavior: by instrumenting finish_command() I found that in all other\n> tests where 'git push' is expected to fail it errors out gracefully\n> and waits for its 'git receive-pack' child process.\n\nAtomic push may have the same problem.\n"},{"id":"414590","messageId":"20210118133011.15331-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20210117222151.GY8396@szeder.dev","subject":"[PATCH 2/2] t5411: refactor check of refs using test_cmp_refs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2021-01-18T13:30:11Z","receivedAt":"2021-01-18T13:33:11Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd new helper 'test_cmp_refs' to check references in a repository.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411/common-functions.sh                   | 15 +++++++\n t/t5411/once-0010-report-status-v1.sh         |  5 +--\n t/t5411/test-0000-standard-git-push.sh        | 24 ++++------\n .../test-0001-standard-git-push--porcelain.sh | 24 ++++------\n t/t5411/test-0002-pre-receive-declined.sh     |  6 +--\n ...st-0003-pre-receive-declined--porcelain.sh |  6 +--\n t/t5411/test-0011-no-hook-error.sh            | 12 ++---\n t/t5411/test-0012-no-hook-error--porcelain.sh | 12 ++---\n t/t5411/test-0013-bad-protocol.sh             | 45 ++++---------------\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 45 ++++---------------\n t/t5411/test-0020-report-ng.sh                | 12 ++---\n t/t5411/test-0021-report-ng--porcelain.sh     | 12 ++---\n t/t5411/test-0022-report-unexpect-ref.sh      |  6 +--\n ...est-0023-report-unexpect-ref--porcelain.sh |  6 +--\n t/t5411/test-0024-report-unknown-ref.sh       |  6 +--\n ...test-0025-report-unknown-ref--porcelain.sh |  6 +--\n t/t5411/test-0026-push-options.sh             | 18 +++-----\n t/t5411/test-0027-push-options--porcelain.sh  | 18 +++-----\n t/t5411/test-0030-report-ok.sh                |  6 +--\n t/t5411/test-0031-report-ok--porcelain.sh     |  6 +--\n t/t5411/test-0032-report-with-options.sh      |  5 +--\n ...est-0033-report-with-options--porcelain.sh |  5 +--\n t/t5411/test-0034-report-ft.sh                |  6 +--\n t/t5411/test-0035-report-ft--porcelain.sh     |  6 +--\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 18 +++-----\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 18 +++-----\n t/t5411/test-0038-report-mixed-refs.sh        |  6 +--\n .../test-0039-report-mixed-refs--porcelain.sh |  6 +--\n t/t5411/test-0040-process-all-refs.sh         |  6 +--\n .../test-0041-process-all-refs--porcelain.sh  |  6 +--\n ...t-0050-proc-receive-refs-with-modifiers.sh | 18 +++-----\n 31 files changed, 126 insertions(+), 264 deletions(-)\n\ndiff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\nindex 344d13f61a..4835dd5182 100644\n--- a/t/t5411/common-functions.sh\n+++ b/t/t5411/common-functions.sh\n@@ -59,3 +59,18 @@ filter_out_user_friendly_and_stable_output () {\n \tmake_user_friendly_and_stable_output |\n \t\tsed -n ${1+\"$@\"}\n }\n+\n+test_cmp_refs () {\n+\tindir=\n+\tif test \"$1\" = \"-C\"\n+\tthen\n+\t\tshift\n+\t\tindir=\"$1\"\n+\t\tshift\n+\tfi\n+\tindir=${indir:+\"$indir\"/}\n+\tcat >show-ref.expect &&\n+\tgit ${indir:+ -C \"$indir\"} show-ref >show-ref.pristine &&\n+\tmake_user_friendly_and_stable_output <show-ref.pristine >show-ref.filtered &&\n+\ttest_cmp show-ref.expect show-ref.filtered\n+}\ndiff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh\nindex cb431a9c91..1233a46eac 100644\n--- a/t/t5411/once-0010-report-status-v1.sh\n+++ b/t/t5411/once-0010-report-status-v1.sh\n@@ -83,12 +83,9 @@ test_expect_success \"proc-receive: report status v1\" '\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/for/main/topic1\n \t<COMMIT-A> refs/heads/foo\n \t<COMMIT-B> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh\nindex 694d8e8dc2..7af6026749 100644\n--- a/t/t5411/test-0000-standard-git-push.sh\n+++ b/t/t5411/test-0000-standard-git-push.sh\n@@ -19,13 +19,11 @@ test_expect_success \"git-push ($PROTOCOL)\" '\n \t * [new branch] HEAD -> next\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)  next(A)\n@@ -48,13 +46,11 @@ test_expect_success \"git-push --atomic ($PROTOCOL)\" '\n \t ! [rejected] <COMMIT-B> -> next (atomic push failed)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)  next(A)\n@@ -81,13 +77,11 @@ test_expect_success \"non-fast-forward git-push ($PROTOCOL)\" '\n \t ! [rejected] main -> main (non-fast-forward)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \t<COMMIT-B> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)  next(B)\n@@ -123,15 +117,13 @@ test_expect_success \"git-push -f ($PROTOCOL)\" '\n \t * [new branch] HEAD -> a/b/c\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/a/b/c\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/review/main/topic\n \t<TAG-v123> refs/tags/v123\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)  tags/v123  refs/review/main/topic(A)  a/b/c(A)\ndiff --git a/t/t5411/test-0001-standard-git-push--porcelain.sh b/t/t5411/test-0001-standard-git-push--porcelain.sh\nindex 6cf0bc7034..9e5c40f39c 100644\n--- a/t/t5411/test-0001-standard-git-push--porcelain.sh\n+++ b/t/t5411/test-0001-standard-git-push--porcelain.sh\n@@ -20,13 +20,11 @@ test_expect_success \"git-push ($PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)  next(A)\n@@ -50,13 +48,11 @@ test_expect_success \"git-push --atomic ($PROTOCOL/porcelain)\" '\n \t!    <COMMIT-B>:refs/heads/next    [rejected] (atomic push failed)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)  next(A)\n@@ -84,13 +80,11 @@ test_expect_success \"non-fast-forward git-push ($PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \t<COMMIT-B> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)  next(B)\n@@ -127,15 +121,13 @@ test_expect_success \"git-push -f ($PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/a/b/c\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/review/main/topic\n \t<TAG-v123> refs/tags/v123\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)  tags/v123  refs/review/main/topic(A)  a/b/c(A)\ndiff --git a/t/t5411/test-0002-pre-receive-declined.sh b/t/t5411/test-0002-pre-receive-declined.sh\nindex 61c3ee1b7f..7c70d45057 100644\n--- a/t/t5411/test-0002-pre-receive-declined.sh\n+++ b/t/t5411/test-0002-pre-receive-declined.sh\n@@ -22,12 +22,10 @@ test_expect_success \"git-push is declined ($PROTOCOL)\" '\n \t ! [remote rejected] HEAD -> next (pre-receive hook declined)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"cleanup ($PROTOCOL)\" '\ndiff --git a/t/t5411/test-0003-pre-receive-declined--porcelain.sh b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\nindex 0a9cc2d44a..ea38248fc5 100644\n--- a/t/t5411/test-0003-pre-receive-declined--porcelain.sh\n+++ b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\n@@ -23,12 +23,10 @@ test_expect_success \"git-push is declined ($PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\ndiff --git a/t/t5411/test-0011-no-hook-error.sh b/t/t5411/test-0011-no-hook-error.sh\nindex fd2bd20b76..778b833f9d 100644\n--- a/t/t5411/test-0011-no-hook-error.sh\n+++ b/t/t5411/test-0011-no-hook-error.sh\n@@ -21,13 +21,11 @@ test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL)\n \t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             next(A)\n@@ -59,10 +57,8 @@ test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCO\n \t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0012-no-hook-error--porcelain.sh b/t/t5411/test-0012-no-hook-error--porcelain.sh\nindex 025a9c7186..371831d170 100644\n--- a/t/t5411/test-0012-no-hook-error--porcelain.sh\n+++ b/t/t5411/test-0012-no-hook-error--porcelain.sh\n@@ -22,13 +22,11 @@ test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL/\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             next(A)\n@@ -61,10 +59,8 @@ test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCO\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh\nindex d20a4e6681..43d37f4109 100644\n--- a/t/t5411/test-0013-bad-protocol.sh\n+++ b/t/t5411/test-0013-bad-protocol.sh\n@@ -36,12 +36,9 @@ test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL)\" '\n \tEOF\n \ttest_cmp expect actual-error &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-read-version, $PROTOCOL)\" '\n@@ -72,12 +69,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-version, $PROTO\n \t# Prevent accidential changes by the internal \"receive-pack\" process.\n \trm out &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-write-version, $PROTOCOL)\" '\n@@ -108,12 +102,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-write-version, $PROT\n \t# Prevent accidential changes by the internal \"receive-pack\" process.\n \trm out &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-read-commands, $PROTOCOL)\" '\n@@ -143,12 +134,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-commands, $PROT\n \t# Prevent accidential changes by the internal \"receive-pack\" process.\n \trm out &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-read-push-options, $PROTOCOL)\" '\n@@ -180,12 +168,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-push-options, $\n \t# Prevent accidential changes by the internal \"receive-pack\" process.\n \trm out &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-write-report, $PROTOCOL)\" '\n@@ -215,12 +200,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-write-report, $PROTO\n \t# Prevent accidential changes by the internal \"receive-pack\" process.\n \trm out &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (no report, $PROTOCOL)\" '\n@@ -254,13 +236,10 @@ test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL)\" '\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             next(A)\n@@ -300,12 +279,9 @@ test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL)\" '\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL)\" '\n@@ -338,10 +314,7 @@ test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL)\" '\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0014-bad-protocol--porcelain.sh b/t/t5411/test-0014-bad-protocol--porcelain.sh\nindex a286030f3e..c6299b4849 100644\n--- a/t/t5411/test-0014-bad-protocol--porcelain.sh\n+++ b/t/t5411/test-0014-bad-protocol--porcelain.sh\n@@ -36,12 +36,9 @@ test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL/porc\n \tEOF\n \ttest_cmp expect actual-error &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-read-version, $PROTOCOL/porcelain)\" '\n@@ -72,12 +69,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-version, $PROTO\n \t# Prevent accidential changes by the internal \"receive-pack\" process.\n \trm out &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-write-version, $PROTOCOL/porcelain)\" '\n@@ -108,12 +102,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-write-version, $PROT\n \t# Prevent accidential changes by the internal \"receive-pack\" process.\n \trm out &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-read-commands, $PROTOCOL/porcelain)\" '\n@@ -143,12 +134,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-commands, $PROT\n \t# Prevent accidential changes by the internal \"receive-pack\" process.\n \trm out &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-read-push-options, $PROTOCOL/porcelain)\" '\n@@ -180,12 +168,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-push-options, $\n \t# Prevent accidential changes by the internal \"receive-pack\" process.\n \trm out &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-write-report, $PROTOCOL/porcelain)\" '\n@@ -215,12 +200,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-write-report, $PROTO\n \t# Prevent accidential changes by the internal \"receive-pack\" process.\n \trm out &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (no report, $PROTOCOL/porcelain)\" '\n@@ -255,13 +237,10 @@ test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL/porcelain)\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             next(A)\n@@ -301,12 +280,9 @@ test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)\" '\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL/porcelain)\" '\n@@ -340,10 +316,7 @@ test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL/porce\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0020-report-ng.sh b/t/t5411/test-0020-report-ng.sh\nindex 9f71da9fb6..37300d374b 100644\n--- a/t/t5411/test-0020-report-ng.sh\n+++ b/t/t5411/test-0020-report-ng.sh\n@@ -26,12 +26,10 @@ test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL)\" '\n \t ! [remote rejected] HEAD -> refs/for/main/topic (failed)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL)\" '\n@@ -62,10 +60,8 @@ test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL)\"\n \t ! [remote rejected] HEAD -> refs/for/main/topic (error msg)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0021-report-ng--porcelain.sh b/t/t5411/test-0021-report-ng--porcelain.sh\nindex 96065e3537..c40eb1060b 100644\n--- a/t/t5411/test-0021-report-ng--porcelain.sh\n+++ b/t/t5411/test-0021-report-ng--porcelain.sh\n@@ -27,12 +27,10 @@ test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL/por\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL/porcelain)\" '\n@@ -64,10 +62,8 @@ test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL/p\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0022-report-unexpect-ref.sh b/t/t5411/test-0022-report-unexpect-ref.sh\nindex 624dda7acc..6c1716b25a 100644\n--- a/t/t5411/test-0022-report-unexpect-ref.sh\n+++ b/t/t5411/test-0022-report-unexpect-ref.sh\n@@ -32,12 +32,10 @@ test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL)\" '\n \t ! [remote rejected] HEAD -> refs/for/main/topic (proc-receive failed to report status)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)\ndiff --git a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\nindex dcb804afc8..46f44cdad5 100644\n--- a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n+++ b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n@@ -33,12 +33,10 @@ test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL/porcelain)\"\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)\ndiff --git a/t/t5411/test-0024-report-unknown-ref.sh b/t/t5411/test-0024-report-unknown-ref.sh\nindex af65355862..a543c5acf7 100644\n--- a/t/t5411/test-0024-report-unknown-ref.sh\n+++ b/t/t5411/test-0024-report-unknown-ref.sh\n@@ -27,10 +27,8 @@ test_expect_success \"proc-receive: report unknown reference ($PROTOCOL)\" '\n \t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (proc-receive failed to report status)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0025-report-unknown-ref--porcelain.sh b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\nindex c336ec0e98..972df50ca7 100644\n--- a/t/t5411/test-0025-report-unknown-ref--porcelain.sh\n+++ b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\n@@ -28,10 +28,8 @@ test_expect_success \"proc-receive: report unknown reference ($PROTOCOL/porcelain\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh\nindex 46b1064fe6..049a474a0c 100644\n--- a/t/t5411/test-0026-push-options.sh\n+++ b/t/t5411/test-0026-push-options.sh\n@@ -22,12 +22,10 @@ test_expect_success \"proc-receive: not support push options ($PROTOCOL)\" '\n \trm out &&\n \ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n \t\tactual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"enable push options ($PROTOCOL)\" '\n@@ -71,13 +69,11 @@ test_expect_success \"proc-receive: ignore push-options for version 0 ($PROTOCOL)\n \t * [new reference] HEAD -> refs/for/main/topic\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"restore proc-receive hook ($PROTOCOL)\" '\n@@ -125,13 +121,11 @@ test_expect_success \"proc-receive: push with options ($PROTOCOL)\" '\n \t * [new reference] HEAD -> refs/for/main/topic\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             next(A)\ndiff --git a/t/t5411/test-0027-push-options--porcelain.sh b/t/t5411/test-0027-push-options--porcelain.sh\nindex 79a49a638e..e0455b8b81 100644\n--- a/t/t5411/test-0027-push-options--porcelain.sh\n+++ b/t/t5411/test-0027-push-options--porcelain.sh\n@@ -23,12 +23,10 @@ test_expect_success \"proc-receive: not support push options ($PROTOCOL/porcelain\n \trm out &&\n \ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n \t\tactual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"enable push options ($PROTOCOL/porcelain)\" '\n@@ -74,13 +72,11 @@ test_expect_success \"proc-receive: ignore push-options for version 0 ($PROTOCOL/\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"restore proc-receive hook ($PROTOCOL/porcelain)\" '\n@@ -130,13 +126,11 @@ test_expect_success \"proc-receive: push with options ($PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             next(A)\ndiff --git a/t/t5411/test-0030-report-ok.sh b/t/t5411/test-0030-report-ok.sh\nindex 5d6feef118..8acb4f204f 100644\n--- a/t/t5411/test-0030-report-ok.sh\n+++ b/t/t5411/test-0030-report-ok.sh\n@@ -26,10 +26,8 @@ test_expect_success \"proc-receive: ok ($PROTOCOL)\" '\n \t * [new reference] HEAD -> refs/for/main/topic\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0031-report-ok--porcelain.sh b/t/t5411/test-0031-report-ok--porcelain.sh\nindex 91666d32df..a967718046 100644\n--- a/t/t5411/test-0031-report-ok--porcelain.sh\n+++ b/t/t5411/test-0031-report-ok--porcelain.sh\n@@ -27,10 +27,8 @@ test_expect_success \"proc-receive: ok ($PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nindex 4d058cc6ff..9711d69f1d 100644\n--- a/t/t5411/test-0032-report-with-options.sh\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -249,10 +249,7 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nindex 8ab908a6b0..7e699e8088 100644\n--- a/t/t5411/test-0033-report-with-options--porcelain.sh\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -258,10 +258,7 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porc\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0034-report-ft.sh b/t/t5411/test-0034-report-ft.sh\nindex c355c290d2..6e0d08b327 100644\n--- a/t/t5411/test-0034-report-ft.sh\n+++ b/t/t5411/test-0034-report-ft.sh\n@@ -28,13 +28,11 @@ test_expect_success \"proc-receive: fall throught, let receive-pack to execute ($\n \t * [new reference] <COMMIT-B> -> refs/for/main/topic\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/for/main/topic\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             refs/for/main/topic(A)\ndiff --git a/t/t5411/test-0035-report-ft--porcelain.sh b/t/t5411/test-0035-report-ft--porcelain.sh\nindex 8ce4e58f2a..81bae9f2ec 100644\n--- a/t/t5411/test-0035-report-ft--porcelain.sh\n+++ b/t/t5411/test-0035-report-ft--porcelain.sh\n@@ -29,13 +29,11 @@ test_expect_success \"proc-receive: fall throught, let receive-pack to execute ($\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/for/main/topic\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             refs/for/main/topic(A)\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex fad8eea8a0..be9b18b2b6 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -65,12 +65,10 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \t <OID-A>..<OID-B> HEAD -> refs/changes/25/125/1\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"proc-receive: check remote-tracking #1 ($PROTOCOL)\" '\n@@ -142,12 +140,10 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \t + <OID-B>...<OID-A> HEAD -> refs/changes/25/125/1 (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"proc-receive: check remote-tracking #2 ($PROTOCOL)\" '\n@@ -205,12 +201,10 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n \t <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"proc-receive: check remote-tracking #3 ($PROTOCOL)\" '\ndiff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\nindex dc254d57eb..95fb89c031 100644\n--- a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n@@ -51,12 +51,10 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL/porcelain)\" '\n@@ -114,12 +112,10 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL/porcelain)\" '\n@@ -163,10 +159,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porc\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nindex 264a5d5c6d..88fa60e53c 100644\n--- a/t/t5411/test-0038-report-mixed-refs.sh\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -67,15 +67,13 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n \t ! [remote rejected] HEAD -> refs/for/next/topic3 (proc-receive failed to report status)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/bar\n \t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/foo\n \t<COMMIT-B> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)             foo(A)  bar(A))  baz(A)\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nindex 1e9c06d2b7..6c36d9250f 100644\n--- a/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -68,15 +68,13 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcel\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/bar\n \t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/foo\n \t<COMMIT-B> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)             foo(A)  bar(A))  baz(A)\ndiff --git a/t/t5411/test-0040-process-all-refs.sh b/t/t5411/test-0040-process-all-refs.sh\nindex 2565302a17..fdcdcc7c2e 100644\n--- a/t/t5411/test-0040-process-all-refs.sh\n+++ b/t/t5411/test-0040-process-all-refs.sh\n@@ -92,14 +92,12 @@ test_expect_success \"proc-receive: process all refs ($PROTOCOL)\" '\n \t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/bar\n \t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             bar(A)  baz(B)\ndiff --git a/t/t5411/test-0041-process-all-refs--porcelain.sh b/t/t5411/test-0041-process-all-refs--porcelain.sh\nindex e21420b60d..73b35fe0aa 100644\n--- a/t/t5411/test-0041-process-all-refs--porcelain.sh\n+++ b/t/t5411/test-0041-process-all-refs--porcelain.sh\n@@ -93,14 +93,12 @@ test_expect_success \"proc-receive: process all refs ($PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/bar\n \t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             bar(A)  baz(B)\ndiff --git a/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh b/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh\nindex 2e29518ec5..7214647ada 100644\n--- a/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh\n+++ b/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh\n@@ -50,12 +50,10 @@ test_expect_success \"proc-receive: update branch and new tag ($PROTOCOL)\" '\n \t * [new reference] v123 -> refs/pull/124/head\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)\n@@ -63,14 +61,12 @@ test_expect_success \"proc-receive: update branch and new tag ($PROTOCOL)\" '\n test_expect_success \"setup upstream: create tags/v123 ($PROTOCOL)\" '\n \tgit -C \"$upstream\" update-ref refs/heads/topic $A &&\n \tgit -C \"$upstream\" update-ref refs/tags/v123 $TAG &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/topic\n \t<TAG-v123> refs/tags/v123\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n@@ -125,11 +121,9 @@ test_expect_success \"proc-receive: create/delete branch, and delete tag ($PROTOC\n \t * [new reference] <COMMIT-A> -> refs/pull/124/head\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-B> refs/heads/topic\n \tEOF\n-\ttest_cmp expect actual\n '\n-- \n2.28.0.15.gba9e81f0bd\n\n"},{"id":"414591","messageId":"20210118133011.15331-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20210117222151.GY8396@szeder.dev","subject":"[PATCH 1/2] t5411: remove file after use to prevent overwriting","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2021-01-18T13:30:10Z","receivedAt":"2021-01-18T14:01:16Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nSZEDER reported that t5411 failed in Travis CI's s390x environment a\ncouple of times, and could be reproduced with '--stress' test on this\nspecific environment.  The test failure messages might look like this:\n\n    + test_cmp expect actual\n    --- expect      2021-01-17 21:55:23.430750004 +0000\n    +++ actual      2021-01-17 21:55:23.430750004 +0000\n    @@ -1 +1 @@\n    -<COMMIT-A> refs/heads/main\n    +<COMMIT-A> refs/heads/maifatal: the remote end hung up unexpectedly\n    error: last command exited with $?=1\n    not ok 86 - proc-receive: not support push options (builtin protocol)\n\nThe file 'actual' is filtered from the file 'out' which contains result\nof 'git show-ref' command.  Due to the error messages from other process\nis written into the file 'out' accidentally, t5411 failed.  SZEDER finds\nthe root cause of this issue:\n\n - 'git push' is executed with its standard output and error redirected\n   to the file 'out'.\n\n - 'git push' executes 'git receive-pack' internally, which inherits\n   the open file descriptors, so its output and error goes into that\n   same 'out' file.\n\n - 'git push' ends without waiting for the close of 'git-receive-pack'\n   for some cases, and the file 'out' is reused for test of\n   'git show-ref' afterwards.\n\n - A mixture of the output of 'git show-ref' abd 'git receive-pack'\n   leads to this issue.\n\nTo resolve this issue, we can remove the file 'out' after use.  The\nlong-running 'git receive-pack' will not redirect its output to the new\ncreated 'out' file which has a different file descriptor.\n\nReported-by: SZEDER Gábor <szeder.dev@gmail.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411/test-0000-standard-git-push.sh         |  4 ++++\n .../test-0001-standard-git-push--porcelain.sh  |  4 ++++\n t/t5411/test-0002-pre-receive-declined.sh      |  2 ++\n ...est-0003-pre-receive-declined--porcelain.sh |  2 ++\n t/t5411/test-0011-no-hook-error.sh             |  4 ++++\n t/t5411/test-0012-no-hook-error--porcelain.sh  |  4 ++++\n t/t5411/test-0013-bad-protocol.sh              | 18 ++++++++++++++++++\n t/t5411/test-0014-bad-protocol--porcelain.sh   | 18 ++++++++++++++++++\n t/t5411/test-0020-report-ng.sh                 |  4 ++++\n t/t5411/test-0021-report-ng--porcelain.sh      |  4 ++++\n t/t5411/test-0022-report-unexpect-ref.sh       |  2 ++\n ...test-0023-report-unexpect-ref--porcelain.sh |  2 ++\n t/t5411/test-0024-report-unknown-ref.sh        |  2 ++\n .../test-0025-report-unknown-ref--porcelain.sh |  2 ++\n t/t5411/test-0026-push-options.sh              |  2 ++\n t/t5411/test-0027-push-options--porcelain.sh   |  2 ++\n t/t5411/test-0032-report-with-options.sh       |  2 ++\n ...test-0033-report-with-options--porcelain.sh |  2 ++\n t/t5411/test-0038-report-mixed-refs.sh         |  2 ++\n .../test-0039-report-mixed-refs--porcelain.sh  |  2 ++\n 20 files changed, 84 insertions(+)\n\ndiff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh\nindex 47b058af7e..694d8e8dc2 100644\n--- a/t/t5411/test-0000-standard-git-push.sh\n+++ b/t/t5411/test-0000-standard-git-push.sh\n@@ -40,6 +40,8 @@ test_expect_success \"git-push --atomic ($PROTOCOL)\" '\n \t\t-e \"/^To / { p; }\" \\\n \t\t-e \"/^ ! / { p; }\" \\\n \t\t<out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t ! [rejected] main -> main (non-fast-forward)\n@@ -67,6 +69,8 @@ test_expect_success \"non-fast-forward git-push ($PROTOCOL)\" '\n \t\t$B:refs/heads/next \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\ndiff --git a/t/t5411/test-0001-standard-git-push--porcelain.sh b/t/t5411/test-0001-standard-git-push--porcelain.sh\nindex bbead12bbb..6cf0bc7034 100644\n--- a/t/t5411/test-0001-standard-git-push--porcelain.sh\n+++ b/t/t5411/test-0001-standard-git-push--porcelain.sh\n@@ -42,6 +42,8 @@ test_expect_success \"git-push --atomic ($PROTOCOL/porcelain)\" '\n \t\t-e \"/^To / { p; }\" \\\n \t\t-e \"/^! / { p; }\" \\\n \t\t<out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t!    refs/heads/main:refs/heads/main    [rejected] (non-fast-forward)\n@@ -69,6 +71,8 @@ test_expect_success \"non-fast-forward git-push ($PROTOCOL/porcelain)\" '\n \t\t$B:refs/heads/next \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\ndiff --git a/t/t5411/test-0002-pre-receive-declined.sh b/t/t5411/test-0002-pre-receive-declined.sh\nindex e7d113a158..61c3ee1b7f 100644\n--- a/t/t5411/test-0002-pre-receive-declined.sh\n+++ b/t/t5411/test-0002-pre-receive-declined.sh\n@@ -14,6 +14,8 @@ test_expect_success \"git-push is declined ($PROTOCOL)\" '\n \t\tHEAD:refs/heads/next \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t ! [remote rejected] <COMMIT-B> -> main (pre-receive hook declined)\ndiff --git a/t/t5411/test-0003-pre-receive-declined--porcelain.sh b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\nindex cc0cca6a47..0a9cc2d44a 100644\n--- a/t/t5411/test-0003-pre-receive-declined--porcelain.sh\n+++ b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\n@@ -14,6 +14,8 @@ test_expect_success \"git-push is declined ($PROTOCOL/porcelain)\" '\n \t\tHEAD:refs/heads/next \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t!    <COMMIT-B>:refs/heads/main    [remote rejected] (pre-receive hook declined)\ndiff --git a/t/t5411/test-0011-no-hook-error.sh b/t/t5411/test-0011-no-hook-error.sh\nindex c50830982f..fd2bd20b76 100644\n--- a/t/t5411/test-0011-no-hook-error.sh\n+++ b/t/t5411/test-0011-no-hook-error.sh\n@@ -7,6 +7,8 @@ test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL)\n \t\tHEAD:refs/for/main/topic \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n@@ -43,6 +45,8 @@ test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCO\n \t\tHEAD:next \\\n \t\tHEAD:refs/for/main/topic >out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main\ndiff --git a/t/t5411/test-0012-no-hook-error--porcelain.sh b/t/t5411/test-0012-no-hook-error--porcelain.sh\nindex 14ea433481..025a9c7186 100644\n--- a/t/t5411/test-0012-no-hook-error--porcelain.sh\n+++ b/t/t5411/test-0012-no-hook-error--porcelain.sh\n@@ -7,6 +7,8 @@ test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL/\n \t\tHEAD:refs/for/main/topic \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n@@ -44,6 +46,8 @@ test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCO\n \t\tHEAD:next \\\n \t\tHEAD:refs/for/main/topic >out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main\ndiff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh\nindex b9be12be77..d20a4e6681 100644\n--- a/t/t5411/test-0013-bad-protocol.sh\n+++ b/t/t5411/test-0013-bad-protocol.sh\n@@ -13,6 +13,8 @@ test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL)\" '\n \t\tHEAD:refs/for/main/topic \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \n \t# Check status report for git-push\n \tsed -n \\\n@@ -67,6 +69,8 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-version, $PROTO\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-read-version option\" out &&\n \tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -101,6 +105,8 @@ test_expect_success \"proc-receive: bad protocol (hook --die-write-version, $PROT\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-write-version option\" out &&\n \tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -134,6 +140,8 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-commands, $PROT\n \tEOF\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-read-commands option\" out &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -169,6 +177,8 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-push-options, $\n \tEOF\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-read-push-options option\" out &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -202,6 +212,8 @@ test_expect_success \"proc-receive: bad protocol (hook --die-write-report, $PROTO\n \tEOF\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-write-report option\" out &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -226,6 +238,8 @@ test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL)\" '\n \t\tHEAD:refs/heads/next \\\n \t\tHEAD:refs/for/main/topic >out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n@@ -272,6 +286,8 @@ test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL)\" '\n \t\tHEAD:refs/for/main/topic\\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n@@ -308,6 +324,8 @@ test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL)\" '\n \t\t\tHEAD:refs/for/main/topic \\\n \t\t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\ndiff --git a/t/t5411/test-0014-bad-protocol--porcelain.sh b/t/t5411/test-0014-bad-protocol--porcelain.sh\nindex fdb4569109..a286030f3e 100644\n--- a/t/t5411/test-0014-bad-protocol--porcelain.sh\n+++ b/t/t5411/test-0014-bad-protocol--porcelain.sh\n@@ -13,6 +13,8 @@ test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL/porc\n \t\tHEAD:refs/for/main/topic \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \n \t# Check status report for git-push\n \tsed -n \\\n@@ -67,6 +69,8 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-version, $PROTO\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-read-version option\" out &&\n \tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -101,6 +105,8 @@ test_expect_success \"proc-receive: bad protocol (hook --die-write-version, $PROT\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-write-version option\" out &&\n \tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -134,6 +140,8 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-commands, $PROT\n \tEOF\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-read-commands option\" out &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -169,6 +177,8 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-push-options, $\n \tEOF\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-read-push-options option\" out &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -202,6 +212,8 @@ test_expect_success \"proc-receive: bad protocol (hook --die-write-report, $PROTO\n \tEOF\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-write-report option\" out &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -226,6 +238,8 @@ test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL/porcelain)\n \t\tHEAD:refs/heads/next \\\n \t\tHEAD:refs/for/main/topic >out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n@@ -272,6 +286,8 @@ test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)\" '\n \t\tHEAD:refs/for/main/topic\\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n@@ -309,6 +325,8 @@ test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL/porce\n \t\t\tHEAD:refs/for/main/topic \\\n \t\t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\ndiff --git a/t/t5411/test-0020-report-ng.sh b/t/t5411/test-0020-report-ng.sh\nindex 5a9e0daf2d..9f71da9fb6 100644\n--- a/t/t5411/test-0020-report-ng.sh\n+++ b/t/t5411/test-0020-report-ng.sh\n@@ -14,6 +14,8 @@ test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL)\" '\n \t\tHEAD:refs/for/main/topic \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n@@ -48,6 +50,8 @@ test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL)\"\n \t\tHEAD:refs/for/main/topic \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\ndiff --git a/t/t5411/test-0021-report-ng--porcelain.sh b/t/t5411/test-0021-report-ng--porcelain.sh\nindex 93475a83cf..96065e3537 100644\n--- a/t/t5411/test-0021-report-ng--porcelain.sh\n+++ b/t/t5411/test-0021-report-ng--porcelain.sh\n@@ -14,6 +14,8 @@ test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL/por\n \t\tHEAD:refs/for/main/topic \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n@@ -49,6 +51,8 @@ test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL/p\n \t\tHEAD:refs/for/main/topic \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\ndiff --git a/t/t5411/test-0022-report-unexpect-ref.sh b/t/t5411/test-0022-report-unexpect-ref.sh\nindex f8be8a0ba1..624dda7acc 100644\n--- a/t/t5411/test-0022-report-unexpect-ref.sh\n+++ b/t/t5411/test-0022-report-unexpect-ref.sh\n@@ -15,6 +15,8 @@ test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL)\" '\n \t\tHEAD:refs/for/main/topic \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main\ndiff --git a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\nindex 778150fa03..dcb804afc8 100644\n--- a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n+++ b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n@@ -15,6 +15,8 @@ test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL/porcelain)\"\n \t\tHEAD:refs/for/main/topic \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main\ndiff --git a/t/t5411/test-0024-report-unknown-ref.sh b/t/t5411/test-0024-report-unknown-ref.sh\nindex d4e74e4681..af65355862 100644\n--- a/t/t5411/test-0024-report-unknown-ref.sh\n+++ b/t/t5411/test-0024-report-unknown-ref.sh\n@@ -14,6 +14,8 @@ test_expect_success \"proc-receive: report unknown reference ($PROTOCOL)\" '\n \t\tHEAD:refs/for/a/b/c/my/topic \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\ndiff --git a/t/t5411/test-0025-report-unknown-ref--porcelain.sh b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\nindex 039e8b6163..c336ec0e98 100644\n--- a/t/t5411/test-0025-report-unknown-ref--porcelain.sh\n+++ b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\n@@ -14,6 +14,8 @@ test_expect_success \"proc-receive: report unknown reference ($PROTOCOL/porcelain\n \t\tHEAD:refs/for/a/b/c/my/topic \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\ndiff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh\nindex e88edb16a4..46b1064fe6 100644\n--- a/t/t5411/test-0026-push-options.sh\n+++ b/t/t5411/test-0026-push-options.sh\n@@ -18,6 +18,8 @@ test_expect_success \"proc-receive: not support push options ($PROTOCOL)\" '\n \t\tHEAD:refs/for/main/topic \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n \t\tactual &&\n \tgit -C \"$upstream\" show-ref >out &&\ndiff --git a/t/t5411/test-0027-push-options--porcelain.sh b/t/t5411/test-0027-push-options--porcelain.sh\nindex 3a6561b5ea..79a49a638e 100644\n--- a/t/t5411/test-0027-push-options--porcelain.sh\n+++ b/t/t5411/test-0027-push-options--porcelain.sh\n@@ -19,6 +19,8 @@ test_expect_success \"proc-receive: not support push options ($PROTOCOL/porcelain\n \t\tHEAD:refs/for/main/topic \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n \t\tactual &&\n \tgit -C \"$upstream\" show-ref >out &&\ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nindex a0faf5c7ff..4d058cc6ff 100644\n--- a/t/t5411/test-0032-report-with-options.sh\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -15,6 +15,8 @@ test_expect_success \"proc-receive: report option without matching ok ($PROTOCOL)\n \t\tHEAD:refs/for/main/topic \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nindex 32ae26bcfb..8ab908a6b0 100644\n--- a/t/t5411/test-0033-report-with-options--porcelain.sh\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -15,6 +15,8 @@ test_expect_success \"proc-receive: report option without matching ok ($PROTOCOL/\n \t\tHEAD:refs/for/main/topic \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nindex 0d071ebaa6..264a5d5c6d 100644\n--- a/t/t5411/test-0038-report-mixed-refs.sh\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -26,6 +26,8 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n \t\tHEAD:refs/for/next/topic3 \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nindex d8409912fd..1e9c06d2b7 100644\n--- a/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -26,6 +26,8 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcel\n \t\tHEAD:refs/for/next/topic3 \\\n \t\t>out 2>&1 &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n+\t# Prevent accidential changes by the internal \"receive-pack\" process.\n+\trm out &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main\n-- \n2.28.0.15.gba9e81f0bd\n\n"},{"id":"414606","messageId":"e1c9c9cf-4498-6861-bb39-53ceeb9c0fe7@kdbg.org","threadId":"52936","inReplyTo":"20210118133011.15331-1-worldhello.net@gmail.com","subject":"Re: [PATCH 1/2] t5411: remove file after use to prevent overwriting","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2021-01-18T18:21:55Z","receivedAt":"2021-01-18T18:23:07Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 18.01.21 um 14:30 schrieb Jiang Xin:\n> From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n> \n> SZEDER reported that t5411 failed in Travis CI's s390x environment a\n> couple of times, and could be reproduced with '--stress' test on this\n> specific environment.  The test failure messages might look like this:\n> \n>     + test_cmp expect actual\n>     --- expect      2021-01-17 21:55:23.430750004 +0000\n>     +++ actual      2021-01-17 21:55:23.430750004 +0000\n>     @@ -1 +1 @@\n>     -<COMMIT-A> refs/heads/main\n>     +<COMMIT-A> refs/heads/maifatal: the remote end hung up unexpectedly\n>     error: last command exited with $?=1\n>     not ok 86 - proc-receive: not support push options (builtin protocol)\n> \n> The file 'actual' is filtered from the file 'out' which contains result\n> of 'git show-ref' command.  Due to the error messages from other process\n> is written into the file 'out' accidentally, t5411 failed.  SZEDER finds\n> the root cause of this issue:\n> \n>  - 'git push' is executed with its standard output and error redirected\n>    to the file 'out'.\n> \n>  - 'git push' executes 'git receive-pack' internally, which inherits\n>    the open file descriptors, so its output and error goes into that\n>    same 'out' file.\n> \n>  - 'git push' ends without waiting for the close of 'git-receive-pack'\n>    for some cases, and the file 'out' is reused for test of\n>    'git show-ref' afterwards.\n> \n>  - A mixture of the output of 'git show-ref' abd 'git receive-pack'\n>    leads to this issue.\n> \n> To resolve this issue, we can remove the file 'out' after use.  The\n> long-running 'git receive-pack' will not redirect its output to the new\n> created 'out' file which has a different file descriptor.\n\nOn Windows, removing an open file is not possible and this...\n\n> diff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh\n> index 47b058af7e..694d8e8dc2 100644\n> --- a/t/t5411/test-0000-standard-git-push.sh\n> +++ b/t/t5411/test-0000-standard-git-push.sh\n> @@ -40,6 +40,8 @@ test_expect_success \"git-push --atomic ($PROTOCOL)\" '\n>  \t\t-e \"/^To / { p; }\" \\\n>  \t\t-e \"/^ ! / { p; }\" \\\n>  \t\t<out >actual &&\n> +\t# Prevent accidential changes by the internal \"receive-pack\" process.\n> +\trm out &&\n\n... would fail.\n\nThat said, your next patch removes a lot of uses of the 'out' file\nagainst which this 'rm out' should protect. Doesn't this make this patch\nunnecessary?\n\n>  \tcat >expect <<-EOF &&\n>  \tTo <URL/of/upstream.git>\n>  \t ! [rejected] main -> main (non-fast-forward)\n\n-- Hannes\n"},{"id":"414647","messageId":"CANYiYbF4gv0x78SX=TbQKP+GwUVCCRpNob+riRs5wPiFKcsSsQ@mail.gmail.com","threadId":"52936","inReplyTo":"e1c9c9cf-4498-6861-bb39-53ceeb9c0fe7@kdbg.org","subject":"Re: [PATCH 1/2] t5411: remove file after use to prevent overwriting","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2021-01-19T00:48:48Z","receivedAt":"2021-01-19T00:49:55Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Johannes Sixt <j6t@kdbg.org> 于2021年1月19日周二 上午2:21写道：\n>\n> Am 18.01.21 um 14:30 schrieb Jiang Xin:\n> > From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n> >\n> > SZEDER reported that t5411 failed in Travis CI's s390x environment a\n> > couple of times, and could be reproduced with '--stress' test on this\n> > specific environment.  The test failure messages might look like this:\n> >\n> >     + test_cmp expect actual\n> >     --- expect      2021-01-17 21:55:23.430750004 +0000\n> >     +++ actual      2021-01-17 21:55:23.430750004 +0000\n> >     @@ -1 +1 @@\n> >     -<COMMIT-A> refs/heads/main\n> >     +<COMMIT-A> refs/heads/maifatal: the remote end hung up unexpectedly\n> >     error: last command exited with $?=1\n> >     not ok 86 - proc-receive: not support push options (builtin protocol)\n> >\n> > The file 'actual' is filtered from the file 'out' which contains result\n> > of 'git show-ref' command.  Due to the error messages from other process\n> > is written into the file 'out' accidentally, t5411 failed.  SZEDER finds\n> > the root cause of this issue:\n> >\n> >  - 'git push' is executed with its standard output and error redirected\n> >    to the file 'out'.\n> >\n> >  - 'git push' executes 'git receive-pack' internally, which inherits\n> >    the open file descriptors, so its output and error goes into that\n> >    same 'out' file.\n> >\n> >  - 'git push' ends without waiting for the close of 'git-receive-pack'\n> >    for some cases, and the file 'out' is reused for test of\n> >    'git show-ref' afterwards.\n> >\n> >  - A mixture of the output of 'git show-ref' abd 'git receive-pack'\n> >    leads to this issue.\n> >\n> > To resolve this issue, we can remove the file 'out' after use.  The\n> > long-running 'git receive-pack' will not redirect its output to the new\n> > created 'out' file which has a different file descriptor.\n>\n> On Windows, removing an open file is not possible and this...\n>\n> > diff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh\n> > index 47b058af7e..694d8e8dc2 100644\n> > --- a/t/t5411/test-0000-standard-git-push.sh\n> > +++ b/t/t5411/test-0000-standard-git-push.sh\n> > @@ -40,6 +40,8 @@ test_expect_success \"git-push --atomic ($PROTOCOL)\" '\n> >               -e \"/^To / { p; }\" \\\n> >               -e \"/^ ! / { p; }\" \\\n> >               <out >actual &&\n> > +     # Prevent accidential changes by the internal \"receive-pack\" process.\n> > +     rm out &&\n>\n> ... would fail.\n\nThank you for reminding. It seems that the only way is to use a\ndifferent output file name.\n\n> That said, your next patch removes a lot of uses of the 'out' file\n> against which this 'rm out' should protect. Doesn't this make this patch\n> unnecessary?\n\nThe other test cases also write to the same \"out\" file, so the issue\nstill needs to be fixed.\n\n--\nJiang Xin\n"},{"id":"414677","messageId":"20210119102459.28986-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"e1c9c9cf-4498-6861-bb39-53ceeb9c0fe7@kdbg.org","subject":"[PATCH v2 1/2] t5411: use different out file to prevent overwriting","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2021-01-19T10:24:58Z","receivedAt":"2021-01-19T14:52:29Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nSZEDER reported that t5411 failed in Travis CI's s390x environment a\ncouple of times, and could be reproduced with '--stress' test on this\nspecific environment.  The test failure messages might look like this:\n\n    + test_cmp expect actual\n    --- expect      2021-01-17 21:55:23.430750004 +0000\n    +++ actual      2021-01-17 21:55:23.430750004 +0000\n    @@ -1 +1 @@\n    -<COMMIT-A> refs/heads/main\n    +<COMMIT-A> refs/heads/maifatal: the remote end hung up unexpectedly\n    error: last command exited with $?=1\n    not ok 86 - proc-receive: not support push options (builtin protocol)\n\nThe file 'actual' is filtered from the file 'out' which contains result\nof 'git show-ref' command.  Due to the error messages from other process\nis written into the file 'out' accidentally, t5411 failed.  SZEDER finds\nthe root cause of this issue:\n\n - 'git push' is executed with its standard output and error redirected\n   to the file 'out'.\n\n - 'git push' executes 'git receive-pack' internally, which inherits\n   the open file descriptors, so its output and error goes into that\n   same 'out' file.\n\n - 'git push' ends without waiting for the close of 'git-receive-pack'\n   for some cases, and the file 'out' is reused for test of\n   'git show-ref' afterwards.\n\n - A mixture of the output of 'git show-ref' abd 'git receive-pack'\n   leads to this issue.\n\nThe first intuitive reaction to resolve this issue is to remove the\nfile 'out' after use, so that the newly created file 'out' will have a\ndifferent file descriptor and will not be overwritten by the\n'git receive-pack' process.  But Johannes pointed out that removing an\nopen file is not possible on Windows.  So we use different temporary\nfile names to store the output of 'git push' to solve this issue.\n\nReported-by: SZEDER Gábor <szeder.dev@gmail.com>\nHelped-by: Johannes Sixt <j6t@kdbg.org>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411/test-0000-standard-git-push.sh        |  8 +--\n .../test-0001-standard-git-push--porcelain.sh |  8 +--\n t/t5411/test-0002-pre-receive-declined.sh     |  4 +-\n ...st-0003-pre-receive-declined--porcelain.sh |  4 +-\n t/t5411/test-0011-no-hook-error.sh            |  8 +--\n t/t5411/test-0012-no-hook-error--porcelain.sh |  8 +--\n t/t5411/test-0013-bad-protocol.sh             | 50 +++++++++----------\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 50 +++++++++----------\n t/t5411/test-0020-report-ng.sh                |  8 +--\n t/t5411/test-0021-report-ng--porcelain.sh     |  8 +--\n t/t5411/test-0022-report-unexpect-ref.sh      |  4 +-\n ...est-0023-report-unexpect-ref--porcelain.sh |  4 +-\n t/t5411/test-0024-report-unknown-ref.sh       |  4 +-\n ...test-0025-report-unknown-ref--porcelain.sh |  4 +-\n t/t5411/test-0026-push-options.sh             |  4 +-\n t/t5411/test-0027-push-options--porcelain.sh  |  4 +-\n t/t5411/test-0032-report-with-options.sh      |  4 +-\n ...est-0033-report-with-options--porcelain.sh |  4 +-\n t/t5411/test-0038-report-mixed-refs.sh        |  4 +-\n .../test-0039-report-mixed-refs--porcelain.sh |  4 +-\n 20 files changed, 98 insertions(+), 98 deletions(-)\n\ndiff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh\nindex 47b058af7e..b074417d4b 100644\n--- a/t/t5411/test-0000-standard-git-push.sh\n+++ b/t/t5411/test-0000-standard-git-push.sh\n@@ -35,11 +35,11 @@ test_expect_success \"git-push --atomic ($PROTOCOL)\" '\n \ttest_must_fail git -C workbench push --atomic origin \\\n \t\tmain \\\n \t\t$B:refs/heads/next \\\n-\t\t>out 2>&1 &&\n+\t\t>out-0000-1 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; }\" \\\n \t\t-e \"/^ ! / { p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-0000-1 >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t ! [rejected] main -> main (non-fast-forward)\n@@ -65,8 +65,8 @@ test_expect_success \"non-fast-forward git-push ($PROTOCOL)\" '\n \t\tpush origin \\\n \t\tmain \\\n \t\t$B:refs/heads/next \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0000-2 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0000-2 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\ndiff --git a/t/t5411/test-0001-standard-git-push--porcelain.sh b/t/t5411/test-0001-standard-git-push--porcelain.sh\nindex bbead12bbb..8a0de5cac8 100644\n--- a/t/t5411/test-0001-standard-git-push--porcelain.sh\n+++ b/t/t5411/test-0001-standard-git-push--porcelain.sh\n@@ -36,12 +36,12 @@ test_expect_success \"git-push --atomic ($PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --atomic --porcelain origin \\\n \t\tmain \\\n \t\t$B:refs/heads/next \\\n-\t\t>out 2>&1 &&\n+\t\t>out-0001-1 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"s/^# GETTEXT POISON #//\" \\\n \t\t-e \"/^To / { p; }\" \\\n \t\t-e \"/^! / { p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-0001-1 >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t!    refs/heads/main:refs/heads/main    [rejected] (non-fast-forward)\n@@ -67,8 +67,8 @@ test_expect_success \"non-fast-forward git-push ($PROTOCOL/porcelain)\" '\n \t\tpush --porcelain origin \\\n \t\tmain \\\n \t\t$B:refs/heads/next \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0001-2 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0001-2 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\ndiff --git a/t/t5411/test-0002-pre-receive-declined.sh b/t/t5411/test-0002-pre-receive-declined.sh\nindex e7d113a158..83edcb037b 100644\n--- a/t/t5411/test-0002-pre-receive-declined.sh\n+++ b/t/t5411/test-0002-pre-receive-declined.sh\n@@ -12,8 +12,8 @@ test_expect_success \"git-push is declined ($PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\t$B:refs/heads/main \\\n \t\tHEAD:refs/heads/next \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0002-1 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0002-1 >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t ! [remote rejected] <COMMIT-B> -> main (pre-receive hook declined)\ndiff --git a/t/t5411/test-0003-pre-receive-declined--porcelain.sh b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\nindex cc0cca6a47..8f3bbeccca 100644\n--- a/t/t5411/test-0003-pre-receive-declined--porcelain.sh\n+++ b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\n@@ -12,8 +12,8 @@ test_expect_success \"git-push is declined ($PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\t$B:refs/heads/main \\\n \t\tHEAD:refs/heads/next \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0003-1 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0003-1 >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t!    <COMMIT-B>:refs/heads/main    [remote rejected] (pre-receive hook declined)\ndiff --git a/t/t5411/test-0011-no-hook-error.sh b/t/t5411/test-0011-no-hook-error.sh\nindex c50830982f..11c85a32d5 100644\n--- a/t/t5411/test-0011-no-hook-error.sh\n+++ b/t/t5411/test-0011-no-hook-error.sh\n@@ -5,8 +5,8 @@ test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL)\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:next \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0011-1 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0011-1 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n@@ -41,8 +41,8 @@ test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCO\n \ttest_must_fail git -C workbench push --atomic origin \\\n \t\t$B:main \\\n \t\tHEAD:next \\\n-\t\tHEAD:refs/for/main/topic >out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tHEAD:refs/for/main/topic >out-0011-2 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0011-2 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main\ndiff --git a/t/t5411/test-0012-no-hook-error--porcelain.sh b/t/t5411/test-0012-no-hook-error--porcelain.sh\nindex 14ea433481..f89754b480 100644\n--- a/t/t5411/test-0012-no-hook-error--porcelain.sh\n+++ b/t/t5411/test-0012-no-hook-error--porcelain.sh\n@@ -5,8 +5,8 @@ test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL/\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:next \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0012-1 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0012-1 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n@@ -42,8 +42,8 @@ test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCO\n \ttest_must_fail git -C workbench push --porcelain --atomic origin \\\n \t\t$B:main \\\n \t\tHEAD:next \\\n-\t\tHEAD:refs/for/main/topic >out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tHEAD:refs/for/main/topic >out-0012-2 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0012-2 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main\ndiff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh\nindex b9be12be77..888fd951c3 100644\n--- a/t/t5411/test-0013-bad-protocol.sh\n+++ b/t/t5411/test-0013-bad-protocol.sh\n@@ -11,8 +11,8 @@ test_expect_success \"setup proc-receive hook (unknown version, $PROTOCOL)\" '\n test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0013-1 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0013-1 >actual &&\n \n \t# Check status report for git-push\n \tsed -n \\\n@@ -55,18 +55,18 @@ test_expect_success \"setup proc-receive hook (hook --die-read-version, $PROTOCOL\n test_expect_success \"proc-receive: bad protocol (hook --die-read-version, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n+\t\t>out-0013-2 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; }\" \\\n \t\t-e \"/^ ! / { p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-0013-2 >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgrep \"remote: fatal: die with the --die-read-version option\" out &&\n-\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out &&\n+\tgrep \"remote: fatal: die with the --die-read-version option\" out-0013-2 &&\n+\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out-0013-2 &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -89,18 +89,18 @@ test_expect_success \"setup proc-receive hook (hook --die-write-version, $PROTOCO\n test_expect_success \"proc-receive: bad protocol (hook --die-write-version, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n+\t\t>out-0013-3 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; }\" \\\n \t\t-e \"/^ ! / { p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-0013-3 >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgrep \"remote: fatal: die with the --die-write-version option\" out &&\n-\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out &&\n+\tgrep \"remote: fatal: die with the --die-write-version option\" out-0013-3 &&\n+\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out-0013-3 &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -123,17 +123,17 @@ test_expect_success \"setup proc-receive hook (hook --die-read-commands, $PROTOCO\n test_expect_success \"proc-receive: bad protocol (hook --die-read-commands, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n+\t\t>out-0013-4 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; }\" \\\n \t\t-e \"/^ ! / { p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-0013-4 >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgrep \"remote: fatal: die with the --die-read-commands option\" out &&\n+\tgrep \"remote: fatal: die with the --die-read-commands option\" out-0013-4 &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -158,17 +158,17 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-push-options, $\n \ttest_must_fail git -C workbench push origin \\\n \t\t-o reviewers=user1,user2 \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n+\t\t>out-0013-5 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; }\" \\\n \t\t-e \"/^ ! / { p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-0013-5 >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgrep \"remote: fatal: die with the --die-read-push-options option\" out &&\n+\tgrep \"remote: fatal: die with the --die-read-push-options option\" out-0013-5 &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -191,17 +191,17 @@ test_expect_success \"setup proc-receive hook (hook --die-write-report, $PROTOCOL\n test_expect_success \"proc-receive: bad protocol (hook --die-write-report, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n+\t\t>out-0013-6 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; }\" \\\n \t\t-e \"/^ ! / { p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-0013-6 >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgrep \"remote: fatal: die with the --die-write-report option\" out &&\n+\tgrep \"remote: fatal: die with the --die-write-report option\" out-0013-6 &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -224,8 +224,8 @@ test_expect_success \"setup proc-receive hook (no report, $PROTOCOL)\" '\n test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/heads/next \\\n-\t\tHEAD:refs/for/main/topic >out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tHEAD:refs/for/main/topic >out-0013-7 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0013-7 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n@@ -270,8 +270,8 @@ test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL)\" '\n test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/main/topic\\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0013-8 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0013-8 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n@@ -306,8 +306,8 @@ test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL)\" '\n test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\t\tHEAD:refs/for/main/topic \\\n-\t\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t\t>out-0013-9 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0013-9 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\ndiff --git a/t/t5411/test-0014-bad-protocol--porcelain.sh b/t/t5411/test-0014-bad-protocol--porcelain.sh\nindex fdb4569109..1ecf0a3976 100644\n--- a/t/t5411/test-0014-bad-protocol--porcelain.sh\n+++ b/t/t5411/test-0014-bad-protocol--porcelain.sh\n@@ -11,8 +11,8 @@ test_expect_success \"setup proc-receive hook (unknown version, $PROTOCOL/porcela\n test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0014-1 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0014-1 >actual &&\n \n \t# Check status report for git-push\n \tsed -n \\\n@@ -55,18 +55,18 @@ test_expect_success \"setup proc-receive hook (hook --die-read-version, $PROTOCOL\n test_expect_success \"proc-receive: bad protocol (hook --die-read-version, $PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n+\t\t>out-0014-2 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; n; p; n; p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-0014-2 >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgrep \"remote: fatal: die with the --die-read-version option\" out &&\n-\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out &&\n+\tgrep \"remote: fatal: die with the --die-read-version option\" out-0014-2 &&\n+\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out-0014-2 &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -89,18 +89,18 @@ test_expect_success \"setup proc-receive hook (hook --die-write-version, $PROTOCO\n test_expect_success \"proc-receive: bad protocol (hook --die-write-version, $PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n+\t\t>out-0014-3 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; n; p; n; p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-0014-3 >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgrep \"remote: fatal: die with the --die-write-version option\" out &&\n-\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out &&\n+\tgrep \"remote: fatal: die with the --die-write-version option\" out-0014-3 &&\n+\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out-0014-3 &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -123,17 +123,17 @@ test_expect_success \"setup proc-receive hook (hook --die-read-commands, $PROTOCO\n test_expect_success \"proc-receive: bad protocol (hook --die-read-commands, $PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n+\t\t>out-0014-4 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; n; p; n; p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-0014-4 >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgrep \"remote: fatal: die with the --die-read-commands option\" out &&\n+\tgrep \"remote: fatal: die with the --die-read-commands option\" out-0014-4 &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -158,17 +158,17 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-push-options, $\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\t-o reviewers=user1,user2 \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n+\t\t>out-0014-5 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; n; p; n; p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-0014-5 >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgrep \"remote: fatal: die with the --die-read-push-options option\" out &&\n+\tgrep \"remote: fatal: die with the --die-read-push-options option\" out-0014-5 &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -191,17 +191,17 @@ test_expect_success \"setup proc-receive hook (hook --die-write-report, $PROTOCOL\n test_expect_success \"proc-receive: bad protocol (hook --die-write-report, $PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n+\t\t>out-0014-6 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; n; p; n; p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-0014-6 >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgrep \"remote: fatal: die with the --die-write-report option\" out &&\n+\tgrep \"remote: fatal: die with the --die-write-report option\" out-0014-6 &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -224,8 +224,8 @@ test_expect_success \"setup proc-receive hook (no report, $PROTOCOL/porcelain)\" '\n test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/heads/next \\\n-\t\tHEAD:refs/for/main/topic >out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tHEAD:refs/for/main/topic >out-0014-7 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0014-7 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n@@ -270,8 +270,8 @@ test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL/porcelain)\" '\n test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/for/main/topic\\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0014-8 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0014-8 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n@@ -307,8 +307,8 @@ test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL/porcelai\n test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\t\tHEAD:refs/for/main/topic \\\n-\t\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t\t>out-0014-9 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0014-9 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\ndiff --git a/t/t5411/test-0020-report-ng.sh b/t/t5411/test-0020-report-ng.sh\nindex 5a9e0daf2d..41efafc55e 100644\n--- a/t/t5411/test-0020-report-ng.sh\n+++ b/t/t5411/test-0020-report-ng.sh\n@@ -12,8 +12,8 @@ test_expect_success \"setup proc-receive hook (ng, no message, $PROTOCOL)\" '\n test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0020-1 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0020-1 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n@@ -46,8 +46,8 @@ test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL)\" '\n test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0020-2 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0020-2 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\ndiff --git a/t/t5411/test-0021-report-ng--porcelain.sh b/t/t5411/test-0021-report-ng--porcelain.sh\nindex 93475a83cf..65b22618cc 100644\n--- a/t/t5411/test-0021-report-ng--porcelain.sh\n+++ b/t/t5411/test-0021-report-ng--porcelain.sh\n@@ -12,8 +12,8 @@ test_expect_success \"setup proc-receive hook (ng, no message, $PROTOCOL/porcelai\n test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0021-1 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0021-1 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n@@ -47,8 +47,8 @@ test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL/porcelain)\"\n test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0021-2 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0021-2 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\ndiff --git a/t/t5411/test-0022-report-unexpect-ref.sh b/t/t5411/test-0022-report-unexpect-ref.sh\nindex f8be8a0ba1..4cbd474678 100644\n--- a/t/t5411/test-0022-report-unexpect-ref.sh\n+++ b/t/t5411/test-0022-report-unexpect-ref.sh\n@@ -13,8 +13,8 @@ test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\t$B:refs/heads/main \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0022-1 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0022-1 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main\ndiff --git a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\nindex 778150fa03..7804b87721 100644\n--- a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n+++ b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n@@ -13,8 +13,8 @@ test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL/porcelain)\"\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\t$B:refs/heads/main \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0023-1 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0023-1 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main\ndiff --git a/t/t5411/test-0024-report-unknown-ref.sh b/t/t5411/test-0024-report-unknown-ref.sh\nindex d4e74e4681..a048cb0f02 100644\n--- a/t/t5411/test-0024-report-unknown-ref.sh\n+++ b/t/t5411/test-0024-report-unknown-ref.sh\n@@ -12,8 +12,8 @@ test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n test_expect_success \"proc-receive: report unknown reference ($PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/a/b/c/my/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0024-1 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0024-1 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\ndiff --git a/t/t5411/test-0025-report-unknown-ref--porcelain.sh b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\nindex 039e8b6163..b3c4fe6330 100644\n--- a/t/t5411/test-0025-report-unknown-ref--porcelain.sh\n+++ b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\n@@ -12,8 +12,8 @@ test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL/porcelai\n test_expect_success \"proc-receive: report unknown reference ($PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/for/a/b/c/my/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0025-1 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0025-1 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\ndiff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh\nindex e88edb16a4..7bc627cee6 100644\n--- a/t/t5411/test-0026-push-options.sh\n+++ b/t/t5411/test-0026-push-options.sh\n@@ -16,8 +16,8 @@ test_expect_success \"proc-receive: not support push options ($PROTOCOL)\" '\n \t\t-o reviewer=user1 \\\n \t\torigin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0026-1 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0026-1 >actual &&\n \ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n \t\tactual &&\n \tgit -C \"$upstream\" show-ref >out &&\ndiff --git a/t/t5411/test-0027-push-options--porcelain.sh b/t/t5411/test-0027-push-options--porcelain.sh\nindex 3a6561b5ea..5b4884a260 100644\n--- a/t/t5411/test-0027-push-options--porcelain.sh\n+++ b/t/t5411/test-0027-push-options--porcelain.sh\n@@ -17,8 +17,8 @@ test_expect_success \"proc-receive: not support push options ($PROTOCOL/porcelain\n \t\t-o reviewer=user1 \\\n \t\torigin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0027-1 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0027-1 >actual &&\n \ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n \t\tactual &&\n \tgit -C \"$upstream\" show-ref >out &&\ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nindex a0faf5c7ff..f5c67a1cc3 100644\n--- a/t/t5411/test-0032-report-with-options.sh\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -13,8 +13,8 @@ test_expect_success \"setup proc-receive hook (option without matching ok, $PROTO\n test_expect_success \"proc-receive: report option without matching ok ($PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0032-1 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0032-1 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nindex 32ae26bcfb..35e785a0fb 100644\n--- a/t/t5411/test-0033-report-with-options--porcelain.sh\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -13,8 +13,8 @@ test_expect_success \"setup proc-receive hook (option without matching ok, $PROTO\n test_expect_success \"proc-receive: report option without matching ok ($PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0033-1 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0033-1 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nindex 0d071ebaa6..0c31447aaa 100644\n--- a/t/t5411/test-0038-report-mixed-refs.sh\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -24,8 +24,8 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n \t\tHEAD:refs/heads/foo \\\n \t\tHEAD:refs/for/main/topic \\\n \t\tHEAD:refs/for/next/topic3 \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0038-1 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0038-1 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nindex d8409912fd..7007dd98fc 100644\n--- a/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -24,8 +24,8 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcel\n \t\tHEAD:refs/heads/foo \\\n \t\tHEAD:refs/for/main/topic \\\n \t\tHEAD:refs/for/next/topic3 \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-0039-1 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-0039-1 >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main\n-- \n2.28.0.15.gba9e81f0bd\n\n"},{"id":"414679","messageId":"20210119102459.28986-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"e1c9c9cf-4498-6861-bb39-53ceeb9c0fe7@kdbg.org","subject":"[PATCH v2 2/2] t5411: refactor check of refs using test_cmp_refs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2021-01-19T10:24:59Z","receivedAt":"2021-01-19T14:52:29Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd new helper 'test_cmp_refs' to check references in a repository.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411/common-functions.sh                   | 15 +++++++\n t/t5411/once-0010-report-status-v1.sh         |  5 +--\n t/t5411/test-0000-standard-git-push.sh        | 24 ++++------\n .../test-0001-standard-git-push--porcelain.sh | 24 ++++------\n t/t5411/test-0002-pre-receive-declined.sh     |  6 +--\n ...st-0003-pre-receive-declined--porcelain.sh |  6 +--\n t/t5411/test-0011-no-hook-error.sh            | 12 ++---\n t/t5411/test-0012-no-hook-error--porcelain.sh | 12 ++---\n t/t5411/test-0013-bad-protocol.sh             | 45 ++++---------------\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 45 ++++---------------\n t/t5411/test-0020-report-ng.sh                | 12 ++---\n t/t5411/test-0021-report-ng--porcelain.sh     | 12 ++---\n t/t5411/test-0022-report-unexpect-ref.sh      |  6 +--\n ...est-0023-report-unexpect-ref--porcelain.sh |  6 +--\n t/t5411/test-0024-report-unknown-ref.sh       |  6 +--\n ...test-0025-report-unknown-ref--porcelain.sh |  6 +--\n t/t5411/test-0026-push-options.sh             | 18 +++-----\n t/t5411/test-0027-push-options--porcelain.sh  | 18 +++-----\n t/t5411/test-0030-report-ok.sh                |  6 +--\n t/t5411/test-0031-report-ok--porcelain.sh     |  6 +--\n t/t5411/test-0032-report-with-options.sh      |  5 +--\n ...est-0033-report-with-options--porcelain.sh |  5 +--\n t/t5411/test-0034-report-ft.sh                |  6 +--\n t/t5411/test-0035-report-ft--porcelain.sh     |  6 +--\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 18 +++-----\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 18 +++-----\n t/t5411/test-0038-report-mixed-refs.sh        |  6 +--\n .../test-0039-report-mixed-refs--porcelain.sh |  6 +--\n t/t5411/test-0040-process-all-refs.sh         |  6 +--\n .../test-0041-process-all-refs--porcelain.sh  |  6 +--\n ...t-0050-proc-receive-refs-with-modifiers.sh | 18 +++-----\n 31 files changed, 126 insertions(+), 264 deletions(-)\n\ndiff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\nindex 344d13f61a..4835dd5182 100644\n--- a/t/t5411/common-functions.sh\n+++ b/t/t5411/common-functions.sh\n@@ -59,3 +59,18 @@ filter_out_user_friendly_and_stable_output () {\n \tmake_user_friendly_and_stable_output |\n \t\tsed -n ${1+\"$@\"}\n }\n+\n+test_cmp_refs () {\n+\tindir=\n+\tif test \"$1\" = \"-C\"\n+\tthen\n+\t\tshift\n+\t\tindir=\"$1\"\n+\t\tshift\n+\tfi\n+\tindir=${indir:+\"$indir\"/}\n+\tcat >show-ref.expect &&\n+\tgit ${indir:+ -C \"$indir\"} show-ref >show-ref.pristine &&\n+\tmake_user_friendly_and_stable_output <show-ref.pristine >show-ref.filtered &&\n+\ttest_cmp show-ref.expect show-ref.filtered\n+}\ndiff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh\nindex cb431a9c91..1233a46eac 100644\n--- a/t/t5411/once-0010-report-status-v1.sh\n+++ b/t/t5411/once-0010-report-status-v1.sh\n@@ -83,12 +83,9 @@ test_expect_success \"proc-receive: report status v1\" '\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/for/main/topic1\n \t<COMMIT-A> refs/heads/foo\n \t<COMMIT-B> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh\nindex b074417d4b..63e0536727 100644\n--- a/t/t5411/test-0000-standard-git-push.sh\n+++ b/t/t5411/test-0000-standard-git-push.sh\n@@ -19,13 +19,11 @@ test_expect_success \"git-push ($PROTOCOL)\" '\n \t * [new branch] HEAD -> next\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)  next(A)\n@@ -46,13 +44,11 @@ test_expect_success \"git-push --atomic ($PROTOCOL)\" '\n \t ! [rejected] <COMMIT-B> -> next (atomic push failed)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)  next(A)\n@@ -77,13 +73,11 @@ test_expect_success \"non-fast-forward git-push ($PROTOCOL)\" '\n \t ! [rejected] main -> main (non-fast-forward)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \t<COMMIT-B> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)  next(B)\n@@ -119,15 +113,13 @@ test_expect_success \"git-push -f ($PROTOCOL)\" '\n \t * [new branch] HEAD -> a/b/c\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/a/b/c\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/review/main/topic\n \t<TAG-v123> refs/tags/v123\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)  tags/v123  refs/review/main/topic(A)  a/b/c(A)\ndiff --git a/t/t5411/test-0001-standard-git-push--porcelain.sh b/t/t5411/test-0001-standard-git-push--porcelain.sh\nindex 8a0de5cac8..4b872eb0a6 100644\n--- a/t/t5411/test-0001-standard-git-push--porcelain.sh\n+++ b/t/t5411/test-0001-standard-git-push--porcelain.sh\n@@ -20,13 +20,11 @@ test_expect_success \"git-push ($PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)  next(A)\n@@ -48,13 +46,11 @@ test_expect_success \"git-push --atomic ($PROTOCOL/porcelain)\" '\n \t!    <COMMIT-B>:refs/heads/next    [rejected] (atomic push failed)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)  next(A)\n@@ -80,13 +76,11 @@ test_expect_success \"non-fast-forward git-push ($PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \t<COMMIT-B> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)  next(B)\n@@ -123,15 +117,13 @@ test_expect_success \"git-push -f ($PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/a/b/c\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/review/main/topic\n \t<TAG-v123> refs/tags/v123\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)  tags/v123  refs/review/main/topic(A)  a/b/c(A)\ndiff --git a/t/t5411/test-0002-pre-receive-declined.sh b/t/t5411/test-0002-pre-receive-declined.sh\nindex 83edcb037b..ce1c18f824 100644\n--- a/t/t5411/test-0002-pre-receive-declined.sh\n+++ b/t/t5411/test-0002-pre-receive-declined.sh\n@@ -20,12 +20,10 @@ test_expect_success \"git-push is declined ($PROTOCOL)\" '\n \t ! [remote rejected] HEAD -> next (pre-receive hook declined)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"cleanup ($PROTOCOL)\" '\ndiff --git a/t/t5411/test-0003-pre-receive-declined--porcelain.sh b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\nindex 8f3bbeccca..7f882d9c12 100644\n--- a/t/t5411/test-0003-pre-receive-declined--porcelain.sh\n+++ b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\n@@ -21,12 +21,10 @@ test_expect_success \"git-push is declined ($PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\ndiff --git a/t/t5411/test-0011-no-hook-error.sh b/t/t5411/test-0011-no-hook-error.sh\nindex 11c85a32d5..3c42995019 100644\n--- a/t/t5411/test-0011-no-hook-error.sh\n+++ b/t/t5411/test-0011-no-hook-error.sh\n@@ -19,13 +19,11 @@ test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL)\n \t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             next(A)\n@@ -55,10 +53,8 @@ test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCO\n \t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0012-no-hook-error--porcelain.sh b/t/t5411/test-0012-no-hook-error--porcelain.sh\nindex f89754b480..b8603607fe 100644\n--- a/t/t5411/test-0012-no-hook-error--porcelain.sh\n+++ b/t/t5411/test-0012-no-hook-error--porcelain.sh\n@@ -20,13 +20,11 @@ test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL/\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             next(A)\n@@ -57,10 +55,8 @@ test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCO\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh\nindex 888fd951c3..895ba8e3b9 100644\n--- a/t/t5411/test-0013-bad-protocol.sh\n+++ b/t/t5411/test-0013-bad-protocol.sh\n@@ -34,12 +34,9 @@ test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL)\" '\n \tEOF\n \ttest_cmp expect actual-error &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-read-version, $PROTOCOL)\" '\n@@ -68,12 +65,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-version, $PROTO\n \tgrep \"remote: fatal: die with the --die-read-version option\" out-0013-2 &&\n \tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out-0013-2 &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-write-version, $PROTOCOL)\" '\n@@ -102,12 +96,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-write-version, $PROT\n \tgrep \"remote: fatal: die with the --die-write-version option\" out-0013-3 &&\n \tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out-0013-3 &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-read-commands, $PROTOCOL)\" '\n@@ -135,12 +126,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-commands, $PROT\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-read-commands option\" out-0013-4 &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-read-push-options, $PROTOCOL)\" '\n@@ -170,12 +158,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-push-options, $\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-read-push-options option\" out-0013-5 &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-write-report, $PROTOCOL)\" '\n@@ -203,12 +188,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-write-report, $PROTO\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-write-report option\" out-0013-6 &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (no report, $PROTOCOL)\" '\n@@ -240,13 +222,10 @@ test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL)\" '\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             next(A)\n@@ -284,12 +263,9 @@ test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL)\" '\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL)\" '\n@@ -320,10 +296,7 @@ test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL)\" '\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0014-bad-protocol--porcelain.sh b/t/t5411/test-0014-bad-protocol--porcelain.sh\nindex 1ecf0a3976..d66b946c4f 100644\n--- a/t/t5411/test-0014-bad-protocol--porcelain.sh\n+++ b/t/t5411/test-0014-bad-protocol--porcelain.sh\n@@ -34,12 +34,9 @@ test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL/porc\n \tEOF\n \ttest_cmp expect actual-error &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-read-version, $PROTOCOL/porcelain)\" '\n@@ -68,12 +65,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-version, $PROTO\n \tgrep \"remote: fatal: die with the --die-read-version option\" out-0014-2 &&\n \tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out-0014-2 &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-write-version, $PROTOCOL/porcelain)\" '\n@@ -102,12 +96,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-write-version, $PROT\n \tgrep \"remote: fatal: die with the --die-write-version option\" out-0014-3 &&\n \tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out-0014-3 &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-read-commands, $PROTOCOL/porcelain)\" '\n@@ -135,12 +126,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-commands, $PROT\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-read-commands option\" out-0014-4 &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-read-push-options, $PROTOCOL/porcelain)\" '\n@@ -170,12 +158,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-push-options, $\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-read-push-options option\" out-0014-5 &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-write-report, $PROTOCOL/porcelain)\" '\n@@ -203,12 +188,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-write-report, $PROTO\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-write-report option\" out-0014-6 &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (no report, $PROTOCOL/porcelain)\" '\n@@ -241,13 +223,10 @@ test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL/porcelain)\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             next(A)\n@@ -285,12 +264,9 @@ test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)\" '\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL/porcelain)\" '\n@@ -322,10 +298,7 @@ test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL/porce\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0020-report-ng.sh b/t/t5411/test-0020-report-ng.sh\nindex 41efafc55e..d3aa071a21 100644\n--- a/t/t5411/test-0020-report-ng.sh\n+++ b/t/t5411/test-0020-report-ng.sh\n@@ -24,12 +24,10 @@ test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL)\" '\n \t ! [remote rejected] HEAD -> refs/for/main/topic (failed)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL)\" '\n@@ -58,10 +56,8 @@ test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL)\"\n \t ! [remote rejected] HEAD -> refs/for/main/topic (error msg)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0021-report-ng--porcelain.sh b/t/t5411/test-0021-report-ng--porcelain.sh\nindex 65b22618cc..9bb82c2a30 100644\n--- a/t/t5411/test-0021-report-ng--porcelain.sh\n+++ b/t/t5411/test-0021-report-ng--porcelain.sh\n@@ -25,12 +25,10 @@ test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL/por\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL/porcelain)\" '\n@@ -60,10 +58,8 @@ test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL/p\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0022-report-unexpect-ref.sh b/t/t5411/test-0022-report-unexpect-ref.sh\nindex 4cbd474678..405c6b3372 100644\n--- a/t/t5411/test-0022-report-unexpect-ref.sh\n+++ b/t/t5411/test-0022-report-unexpect-ref.sh\n@@ -30,12 +30,10 @@ test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL)\" '\n \t ! [remote rejected] HEAD -> refs/for/main/topic (proc-receive failed to report status)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)\ndiff --git a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\nindex 7804b87721..1fe3120741 100644\n--- a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n+++ b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n@@ -31,12 +31,10 @@ test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL/porcelain)\"\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)\ndiff --git a/t/t5411/test-0024-report-unknown-ref.sh b/t/t5411/test-0024-report-unknown-ref.sh\nindex a048cb0f02..b868d0bd7c 100644\n--- a/t/t5411/test-0024-report-unknown-ref.sh\n+++ b/t/t5411/test-0024-report-unknown-ref.sh\n@@ -25,10 +25,8 @@ test_expect_success \"proc-receive: report unknown reference ($PROTOCOL)\" '\n \t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (proc-receive failed to report status)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0025-report-unknown-ref--porcelain.sh b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\nindex b3c4fe6330..813b005797 100644\n--- a/t/t5411/test-0025-report-unknown-ref--porcelain.sh\n+++ b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\n@@ -26,10 +26,8 @@ test_expect_success \"proc-receive: report unknown reference ($PROTOCOL/porcelain\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh\nindex 7bc627cee6..c8e453f993 100644\n--- a/t/t5411/test-0026-push-options.sh\n+++ b/t/t5411/test-0026-push-options.sh\n@@ -20,12 +20,10 @@ test_expect_success \"proc-receive: not support push options ($PROTOCOL)\" '\n \tmake_user_friendly_and_stable_output <out-0026-1 >actual &&\n \ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n \t\tactual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"enable push options ($PROTOCOL)\" '\n@@ -69,13 +67,11 @@ test_expect_success \"proc-receive: ignore push-options for version 0 ($PROTOCOL)\n \t * [new reference] HEAD -> refs/for/main/topic\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"restore proc-receive hook ($PROTOCOL)\" '\n@@ -123,13 +119,11 @@ test_expect_success \"proc-receive: push with options ($PROTOCOL)\" '\n \t * [new reference] HEAD -> refs/for/main/topic\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             next(A)\ndiff --git a/t/t5411/test-0027-push-options--porcelain.sh b/t/t5411/test-0027-push-options--porcelain.sh\nindex 5b4884a260..804d666875 100644\n--- a/t/t5411/test-0027-push-options--porcelain.sh\n+++ b/t/t5411/test-0027-push-options--porcelain.sh\n@@ -21,12 +21,10 @@ test_expect_success \"proc-receive: not support push options ($PROTOCOL/porcelain\n \tmake_user_friendly_and_stable_output <out-0027-1 >actual &&\n \ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n \t\tactual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"enable push options ($PROTOCOL/porcelain)\" '\n@@ -72,13 +70,11 @@ test_expect_success \"proc-receive: ignore push-options for version 0 ($PROTOCOL/\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"restore proc-receive hook ($PROTOCOL/porcelain)\" '\n@@ -128,13 +124,11 @@ test_expect_success \"proc-receive: push with options ($PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             next(A)\ndiff --git a/t/t5411/test-0030-report-ok.sh b/t/t5411/test-0030-report-ok.sh\nindex 5d6feef118..8acb4f204f 100644\n--- a/t/t5411/test-0030-report-ok.sh\n+++ b/t/t5411/test-0030-report-ok.sh\n@@ -26,10 +26,8 @@ test_expect_success \"proc-receive: ok ($PROTOCOL)\" '\n \t * [new reference] HEAD -> refs/for/main/topic\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0031-report-ok--porcelain.sh b/t/t5411/test-0031-report-ok--porcelain.sh\nindex 91666d32df..a967718046 100644\n--- a/t/t5411/test-0031-report-ok--porcelain.sh\n+++ b/t/t5411/test-0031-report-ok--porcelain.sh\n@@ -27,10 +27,8 @@ test_expect_success \"proc-receive: ok ($PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nindex f5c67a1cc3..6040ea7aa7 100644\n--- a/t/t5411/test-0032-report-with-options.sh\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -247,10 +247,7 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nindex 35e785a0fb..6c9b120eaf 100644\n--- a/t/t5411/test-0033-report-with-options--porcelain.sh\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -256,10 +256,7 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porc\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0034-report-ft.sh b/t/t5411/test-0034-report-ft.sh\nindex c355c290d2..6e0d08b327 100644\n--- a/t/t5411/test-0034-report-ft.sh\n+++ b/t/t5411/test-0034-report-ft.sh\n@@ -28,13 +28,11 @@ test_expect_success \"proc-receive: fall throught, let receive-pack to execute ($\n \t * [new reference] <COMMIT-B> -> refs/for/main/topic\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/for/main/topic\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             refs/for/main/topic(A)\ndiff --git a/t/t5411/test-0035-report-ft--porcelain.sh b/t/t5411/test-0035-report-ft--porcelain.sh\nindex 8ce4e58f2a..81bae9f2ec 100644\n--- a/t/t5411/test-0035-report-ft--porcelain.sh\n+++ b/t/t5411/test-0035-report-ft--porcelain.sh\n@@ -29,13 +29,11 @@ test_expect_success \"proc-receive: fall throught, let receive-pack to execute ($\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/for/main/topic\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             refs/for/main/topic(A)\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex fad8eea8a0..be9b18b2b6 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -65,12 +65,10 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \t <OID-A>..<OID-B> HEAD -> refs/changes/25/125/1\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"proc-receive: check remote-tracking #1 ($PROTOCOL)\" '\n@@ -142,12 +140,10 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \t + <OID-B>...<OID-A> HEAD -> refs/changes/25/125/1 (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"proc-receive: check remote-tracking #2 ($PROTOCOL)\" '\n@@ -205,12 +201,10 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n \t <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"proc-receive: check remote-tracking #3 ($PROTOCOL)\" '\ndiff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\nindex dc254d57eb..95fb89c031 100644\n--- a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n@@ -51,12 +51,10 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL/porcelain)\" '\n@@ -114,12 +112,10 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL/porcelain)\" '\n@@ -163,10 +159,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porc\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nindex 0c31447aaa..1fd5bfd1e2 100644\n--- a/t/t5411/test-0038-report-mixed-refs.sh\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -65,15 +65,13 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n \t ! [remote rejected] HEAD -> refs/for/next/topic3 (proc-receive failed to report status)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/bar\n \t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/foo\n \t<COMMIT-B> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)             foo(A)  bar(A))  baz(A)\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nindex 7007dd98fc..23fb796efe 100644\n--- a/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -66,15 +66,13 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcel\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/bar\n \t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/foo\n \t<COMMIT-B> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)             foo(A)  bar(A))  baz(A)\ndiff --git a/t/t5411/test-0040-process-all-refs.sh b/t/t5411/test-0040-process-all-refs.sh\nindex 2565302a17..fdcdcc7c2e 100644\n--- a/t/t5411/test-0040-process-all-refs.sh\n+++ b/t/t5411/test-0040-process-all-refs.sh\n@@ -92,14 +92,12 @@ test_expect_success \"proc-receive: process all refs ($PROTOCOL)\" '\n \t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/bar\n \t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             bar(A)  baz(B)\ndiff --git a/t/t5411/test-0041-process-all-refs--porcelain.sh b/t/t5411/test-0041-process-all-refs--porcelain.sh\nindex e21420b60d..73b35fe0aa 100644\n--- a/t/t5411/test-0041-process-all-refs--porcelain.sh\n+++ b/t/t5411/test-0041-process-all-refs--porcelain.sh\n@@ -93,14 +93,12 @@ test_expect_success \"proc-receive: process all refs ($PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/bar\n \t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             bar(A)  baz(B)\ndiff --git a/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh b/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh\nindex 2e29518ec5..7214647ada 100644\n--- a/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh\n+++ b/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh\n@@ -50,12 +50,10 @@ test_expect_success \"proc-receive: update branch and new tag ($PROTOCOL)\" '\n \t * [new reference] v123 -> refs/pull/124/head\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)\n@@ -63,14 +61,12 @@ test_expect_success \"proc-receive: update branch and new tag ($PROTOCOL)\" '\n test_expect_success \"setup upstream: create tags/v123 ($PROTOCOL)\" '\n \tgit -C \"$upstream\" update-ref refs/heads/topic $A &&\n \tgit -C \"$upstream\" update-ref refs/tags/v123 $TAG &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/topic\n \t<TAG-v123> refs/tags/v123\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n@@ -125,11 +121,9 @@ test_expect_success \"proc-receive: create/delete branch, and delete tag ($PROTOC\n \t * [new reference] <COMMIT-A> -> refs/pull/124/head\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-B> refs/heads/topic\n \tEOF\n-\ttest_cmp expect actual\n '\n-- \n2.28.0.15.gba9e81f0bd\n\n"},{"id":"414686","messageId":"20210119102459.28986-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"e1c9c9cf-4498-6861-bb39-53ceeb9c0fe7@kdbg.org","subject":"[PATCH v2 0/2] t5411 out file overwrite fix","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2021-01-19T10:24:57Z","receivedAt":"2021-01-19T18:27:12Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nt5411 has some test cases on the behavior of failed 'git push'\ncommands.  Even the 'git push' command ended, the output file\nmay still be updated with messages from 'git-receive-pack' command\nrunning in background.  This breaks test cases which reuse the\nsame 'out' file.\n\n## Changes since v1\n\n+ Use different 'out' file instead of removing it which does not work on\n  Windows.\n\n## Range-diff v1..v2\n\n1:  037e52118d ! 1:  3ec70c1c68 t5411: remove file after use to prevent overwriting\n    @@ Metadata\n     Author: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n     \n      ## Commit message ##\n    -    t5411: remove file after use to prevent overwriting\n    +    t5411: use different out file to prevent overwriting\n     \n         SZEDER reported that t5411 failed in Travis CI's s390x environment a\n         couple of times, and could be reproduced with '--stress' test on this\n    @@ Commit message\n          - A mixture of the output of 'git show-ref' abd 'git receive-pack'\n            leads to this issue.\n     \n    -    To resolve this issue, we can remove the file 'out' after use.  The\n    -    long-running 'git receive-pack' will not redirect its output to the new\n    -    created 'out' file which has a different file descriptor.\n    +    The first intuitive reaction to resolve this issue is to remove the\n    +    file 'out' after use, so that the newly created file 'out' will have a\n    +    different file descriptor and will not be overwritten by the\n    +    'git receive-pack' process.  But Johannes pointed out that removing an\n    +    open file is not possible on Windows.  So we use different temporary\n    +    file names to store the output of 'git push' to solve this issue.\n     \n         Reported-by: SZEDER Gábor <szeder.dev@gmail.com>\n    +    Helped-by: Johannes Sixt <j6t@kdbg.org>\n         Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n     \n      ## t/t5411/test-0000-standard-git-push.sh ##\n     @@ t/t5411/test-0000-standard-git-push.sh: test_expect_success \"git-push --atomic ($PROTOCOL)\" '\n\n     ... ...\n\n-- \n\nJiang Xin (2):\n  t5411: use different out file to prevent overwriting\n  t5411: refactor check of refs using test_cmp_refs\n\n t/t5411/common-functions.sh                   | 15 +++\n t/t5411/once-0010-report-status-v1.sh         |  5 +-\n t/t5411/test-0000-standard-git-push.sh        | 32 +++----\n .../test-0001-standard-git-push--porcelain.sh | 32 +++----\n t/t5411/test-0002-pre-receive-declined.sh     | 10 +-\n ...st-0003-pre-receive-declined--porcelain.sh | 10 +-\n t/t5411/test-0011-no-hook-error.sh            | 20 ++--\n t/t5411/test-0012-no-hook-error--porcelain.sh | 20 ++--\n t/t5411/test-0013-bad-protocol.sh             | 95 +++++++------------\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 95 +++++++------------\n t/t5411/test-0020-report-ng.sh                | 20 ++--\n t/t5411/test-0021-report-ng--porcelain.sh     | 20 ++--\n t/t5411/test-0022-report-unexpect-ref.sh      | 10 +-\n ...est-0023-report-unexpect-ref--porcelain.sh | 10 +-\n t/t5411/test-0024-report-unknown-ref.sh       | 10 +-\n ...test-0025-report-unknown-ref--porcelain.sh | 10 +-\n t/t5411/test-0026-push-options.sh             | 22 ++---\n t/t5411/test-0027-push-options--porcelain.sh  | 22 ++---\n t/t5411/test-0030-report-ok.sh                |  6 +-\n t/t5411/test-0031-report-ok--porcelain.sh     |  6 +-\n t/t5411/test-0032-report-with-options.sh      |  9 +-\n ...est-0033-report-with-options--porcelain.sh |  9 +-\n t/t5411/test-0034-report-ft.sh                |  6 +-\n t/t5411/test-0035-report-ft--porcelain.sh     |  6 +-\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 18 ++--\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 18 ++--\n t/t5411/test-0038-report-mixed-refs.sh        | 10 +-\n .../test-0039-report-mixed-refs--porcelain.sh | 10 +-\n t/t5411/test-0040-process-all-refs.sh         |  6 +-\n .../test-0041-process-all-refs--porcelain.sh  |  6 +-\n ...t-0050-proc-receive-refs-with-modifiers.sh | 18 ++--\n 31 files changed, 224 insertions(+), 362 deletions(-)\n\n-- \n2.28.0.15.gba9e81f0bd\n\n"},{"id":"414791","messageId":"20210120122858.GZ8396@szeder.dev","threadId":"52936","inReplyTo":"CANYiYbE358amO95JStnspS-N-9WMYENiYspdKtsGsUc7Hv5jWw@mail.gmail.com","subject":"Re: [PATCH v19 03/10] receive-pack: add new proc-receive hook","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2021-01-20T12:28:58Z","receivedAt":"2021-01-20T13:48:20Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Mon, Jan 18, 2021 at 04:24:11PM +0800, Jiang Xin wrote:\n> SZEDER Gábor <szeder.dev@gmail.com> 于2021年1月18日周一 上午6:21写道：\n> >\n> >\n> > This patch added a whole lot of test cases, and two of them '86 -\n> > proc-receive: not support push options (builtin protocol)' and '95 -\n> > proc-receive: not support push options (builtin protocol/porcelain)'\n> > are prone to rare failures.\n> >\n> > On Thu, Aug 27, 2020 at 11:45:44AM -0400, Jiang Xin wrote:\n> > > diff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh\n> > > new file mode 100644\n> > > index 0000000000..d0c4da8b23\n> > > --- /dev/null\n> > > +++ b/t/t5411/test-0026-push-options.sh\n> >\n> > > +# Refs of upstream : master(A)\n> > > +# Refs of workbench: master(A)  tags/v123\n> > > +# git push -o ...  :                       refs/for/master/topic\n> > > +test_expect_success \"proc-receive: not support push options ($PROTOCOL)\" '\n> > > +     test_must_fail git -C workbench push \\\n> > > +             -o issue=123 \\\n> > > +             -o reviewer=user1 \\\n> > > +             origin \\\n> > > +             HEAD:refs/for/master/topic \\\n> > > +             >out 2>&1 &&\n> >\n> > Three relevant things are happening here:\n> >\n> >   - 'git push' is executed with its standard output and error\n> >     redirected to the file 'out'.\n> >\n> >   - 'git push' executes 'git receive-pack' internally, which inherits\n> >     the open file descriptors, so its output and error goes into that\n> >     same 'out' file.\n> >\n> >   - 'git push' is expected to fail when it finds out that the other\n> >     side doesn't support push options, but it does so with a simple\n> >     die() right away, without waiting for its child 'git receive-pack'\n> >     process to finish.\n> >\n> > > +     make_user_friendly_and_stable_output <out >actual &&\n> > > +     test_i18ngrep \"fatal: the receiving end does not support push options\" \\\n> > > +             actual &&\n> > > +     git -C \"$upstream\" show-ref >out &&\n> >\n> > Here the shell opens and truncates the file 'out' to write 'git\n> > show-ref's output, i.e. it is still the same 'out' file that was used\n> > earlier.\n> >\n> > Consequently, it is possible that 'git receive-pack' is still running,\n> > its open file descriptors to 'out' are still valid, and its \"fatal:\n> > the remote end hung up unexpectedly\" error message about the suddenly\n> > disappeared 'git push' can partially overwrite the output from 'git\n> > show-ref'.\n\n\n> > I think these are the only two tests that can cause this racy\n> > behavior: by instrumenting finish_command() I found that in all other\n> > tests where 'git push' is expected to fail it errors out gracefully\n> > and waits for its 'git receive-pack' child process.\n> \n> Atomic push may have the same problem.\n\nI don't think so, because send_pack() doesn't die() when a ref is\nrejected in an atomic push, but returns, and lets its caller terminate\nin an usual way, including waiting for 'git receive-pack'.\n"},{"id":"414792","messageId":"20210120124906.GA8396@szeder.dev","threadId":"52936","inReplyTo":"20210119102459.28986-2-worldhello.net@gmail.com","subject":"Re: [PATCH v2 1/2] t5411: use different out file to prevent overwriting","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2021-01-20T12:49:06Z","receivedAt":"2021-01-20T14:09:43Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Tue, Jan 19, 2021 at 05:24:58AM -0500, Jiang Xin wrote:\n> From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n> \n> SZEDER reported that t5411 failed in Travis CI's s390x environment a\n> couple of times, and could be reproduced with '--stress' test on this\n> specific environment.  The test failure messages might look like this:\n> \n>     + test_cmp expect actual\n>     --- expect      2021-01-17 21:55:23.430750004 +0000\n>     +++ actual      2021-01-17 21:55:23.430750004 +0000\n>     @@ -1 +1 @@\n>     -<COMMIT-A> refs/heads/main\n>     +<COMMIT-A> refs/heads/maifatal: the remote end hung up unexpectedly\n>     error: last command exited with $?=1\n>     not ok 86 - proc-receive: not support push options (builtin protocol)\n> \n> The file 'actual' is filtered from the file 'out' which contains result\n> of 'git show-ref' command.  Due to the error messages from other process\n> is written into the file 'out' accidentally, t5411 failed.  SZEDER finds\n> the root cause of this issue:\n> \n>  - 'git push' is executed with its standard output and error redirected\n>    to the file 'out'.\n> \n>  - 'git push' executes 'git receive-pack' internally, which inherits\n>    the open file descriptors, so its output and error goes into that\n>    same 'out' file.\n> \n>  - 'git push' ends without waiting for the close of 'git-receive-pack'\n>    for some cases, and the file 'out' is reused for test of\n>    'git show-ref' afterwards.\n> \n>  - A mixture of the output of 'git show-ref' abd 'git receive-pack'\n>    leads to this issue.\n> \n> The first intuitive reaction to resolve this issue is to remove the\n> file 'out' after use, so that the newly created file 'out' will have a\n> different file descriptor and will not be overwritten by the\n> 'git receive-pack' process.  But Johannes pointed out that removing an\n> open file is not possible on Windows.  So we use different temporary\n> file names to store the output of 'git push' to solve this issue.\n> \n> Reported-by: SZEDER Gábor <szeder.dev@gmail.com>\n> Helped-by: Johannes Sixt <j6t@kdbg.org>\n> Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n> ---\n>  t/t5411/test-0000-standard-git-push.sh        |  8 +--\n>  .../test-0001-standard-git-push--porcelain.sh |  8 +--\n>  t/t5411/test-0002-pre-receive-declined.sh     |  4 +-\n>  ...st-0003-pre-receive-declined--porcelain.sh |  4 +-\n>  t/t5411/test-0011-no-hook-error.sh            |  8 +--\n>  t/t5411/test-0012-no-hook-error--porcelain.sh |  8 +--\n>  t/t5411/test-0013-bad-protocol.sh             | 50 +++++++++----------\n>  t/t5411/test-0014-bad-protocol--porcelain.sh  | 50 +++++++++----------\n>  t/t5411/test-0020-report-ng.sh                |  8 +--\n>  t/t5411/test-0021-report-ng--porcelain.sh     |  8 +--\n>  t/t5411/test-0022-report-unexpect-ref.sh      |  4 +-\n>  ...est-0023-report-unexpect-ref--porcelain.sh |  4 +-\n>  t/t5411/test-0024-report-unknown-ref.sh       |  4 +-\n>  ...test-0025-report-unknown-ref--porcelain.sh |  4 +-\n>  t/t5411/test-0026-push-options.sh             |  4 +-\n>  t/t5411/test-0027-push-options--porcelain.sh  |  4 +-\n>  t/t5411/test-0032-report-with-options.sh      |  4 +-\n>  ...est-0033-report-with-options--porcelain.sh |  4 +-\n>  t/t5411/test-0038-report-mixed-refs.sh        |  4 +-\n>  .../test-0039-report-mixed-refs--porcelain.sh |  4 +-\n>  20 files changed, 98 insertions(+), 98 deletions(-)\n\nWow, this is a lot of churn.\n\nWould it be possible/desirable to die gracefully when the receiving\nend doesn't support push options?\n\n> diff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh\n> index 47b058af7e..b074417d4b 100644\n> --- a/t/t5411/test-0000-standard-git-push.sh\n> +++ b/t/t5411/test-0000-standard-git-push.sh\n> @@ -35,11 +35,11 @@ test_expect_success \"git-push --atomic ($PROTOCOL)\" '\n>  \ttest_must_fail git -C workbench push --atomic origin \\\n>  \t\tmain \\\n>  \t\t$B:refs/heads/next \\\n> -\t\t>out 2>&1 &&\n> +\t\t>out-0000-1 2>&1 &&\n>  \tfilter_out_user_friendly_and_stable_output \\\n>  \t\t-e \"/^To / { p; }\" \\\n>  \t\t-e \"/^ ! / { p; }\" \\\n> -\t\t<out >actual &&\n> +\t\t<out-0000-1 >actual &&\n>  \tcat >expect <<-EOF &&\n>  \tTo <URL/of/upstream.git>\n>  \t ! [rejected] main -> main (non-fast-forward)\n> @@ -65,8 +65,8 @@ test_expect_success \"non-fast-forward git-push ($PROTOCOL)\" '\n>  \t\tpush origin \\\n>  \t\tmain \\\n>  \t\t$B:refs/heads/next \\\n> -\t\t>out 2>&1 &&\n> -\tmake_user_friendly_and_stable_output <out >actual &&\n> +\t\t>out-0000-2 2>&1 &&\n> +\tmake_user_friendly_and_stable_output <out-0000-2 >actual &&\n\nThere will be a lot of sequential numbers this way, which might lead\nto additional churn in the future, if we ever were to add more tests\nsomewhere in the middle.  However, our test framework does already\nhave a counter for test cases, so we could perhaps use that, i.e.\n'out-$test_count', to make sure that each test case has its own output\nfile.\n\n"},{"id":"414878","messageId":"20210121025331.21658-3-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20210120124906.GA8396@szeder.dev","subject":"[PATCH v3 2/2] t5411: refactor check of refs using test_cmp_refs","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2021-01-21T02:53:31Z","receivedAt":"2021-01-21T03:50:39Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nAdd new helper 'test_cmp_refs' to check references in a repository.\n\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411/common-functions.sh                   | 15 +++++++\n t/t5411/once-0010-report-status-v1.sh         |  5 +--\n t/t5411/test-0000-standard-git-push.sh        | 24 ++++------\n .../test-0001-standard-git-push--porcelain.sh | 24 ++++------\n t/t5411/test-0002-pre-receive-declined.sh     |  6 +--\n ...st-0003-pre-receive-declined--porcelain.sh |  6 +--\n t/t5411/test-0011-no-hook-error.sh            | 12 ++---\n t/t5411/test-0012-no-hook-error--porcelain.sh | 12 ++---\n t/t5411/test-0013-bad-protocol.sh             | 45 ++++---------------\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 45 ++++---------------\n t/t5411/test-0020-report-ng.sh                | 12 ++---\n t/t5411/test-0021-report-ng--porcelain.sh     | 12 ++---\n t/t5411/test-0022-report-unexpect-ref.sh      |  6 +--\n ...est-0023-report-unexpect-ref--porcelain.sh |  6 +--\n t/t5411/test-0024-report-unknown-ref.sh       |  6 +--\n ...test-0025-report-unknown-ref--porcelain.sh |  6 +--\n t/t5411/test-0026-push-options.sh             | 18 +++-----\n t/t5411/test-0027-push-options--porcelain.sh  | 18 +++-----\n t/t5411/test-0030-report-ok.sh                |  6 +--\n t/t5411/test-0031-report-ok--porcelain.sh     |  6 +--\n t/t5411/test-0032-report-with-options.sh      |  5 +--\n ...est-0033-report-with-options--porcelain.sh |  5 +--\n t/t5411/test-0034-report-ft.sh                |  6 +--\n t/t5411/test-0035-report-ft--porcelain.sh     |  6 +--\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 18 +++-----\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 18 +++-----\n t/t5411/test-0038-report-mixed-refs.sh        |  6 +--\n .../test-0039-report-mixed-refs--porcelain.sh |  6 +--\n t/t5411/test-0040-process-all-refs.sh         |  6 +--\n .../test-0041-process-all-refs--porcelain.sh  |  6 +--\n ...t-0050-proc-receive-refs-with-modifiers.sh | 18 +++-----\n 31 files changed, 126 insertions(+), 264 deletions(-)\n\ndiff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh\nindex 344d13f61a..4835dd5182 100644\n--- a/t/t5411/common-functions.sh\n+++ b/t/t5411/common-functions.sh\n@@ -59,3 +59,18 @@ filter_out_user_friendly_and_stable_output () {\n \tmake_user_friendly_and_stable_output |\n \t\tsed -n ${1+\"$@\"}\n }\n+\n+test_cmp_refs () {\n+\tindir=\n+\tif test \"$1\" = \"-C\"\n+\tthen\n+\t\tshift\n+\t\tindir=\"$1\"\n+\t\tshift\n+\tfi\n+\tindir=${indir:+\"$indir\"/}\n+\tcat >show-ref.expect &&\n+\tgit ${indir:+ -C \"$indir\"} show-ref >show-ref.pristine &&\n+\tmake_user_friendly_and_stable_output <show-ref.pristine >show-ref.filtered &&\n+\ttest_cmp show-ref.expect show-ref.filtered\n+}\ndiff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh\nindex cb431a9c91..1233a46eac 100644\n--- a/t/t5411/once-0010-report-status-v1.sh\n+++ b/t/t5411/once-0010-report-status-v1.sh\n@@ -83,12 +83,9 @@ test_expect_success \"proc-receive: report status v1\" '\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/for/main/topic1\n \t<COMMIT-A> refs/heads/foo\n \t<COMMIT-B> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh\nindex bdc4907191..e1e0175c12 100644\n--- a/t/t5411/test-0000-standard-git-push.sh\n+++ b/t/t5411/test-0000-standard-git-push.sh\n@@ -19,13 +19,11 @@ test_expect_success \"git-push ($PROTOCOL)\" '\n \t * [new branch] HEAD -> next\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)  next(A)\n@@ -46,13 +44,11 @@ test_expect_success \"git-push --atomic ($PROTOCOL)\" '\n \t ! [rejected] <COMMIT-B> -> next (atomic push failed)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)  next(A)\n@@ -77,13 +73,11 @@ test_expect_success \"non-fast-forward git-push ($PROTOCOL)\" '\n \t ! [rejected] main -> main (non-fast-forward)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \t<COMMIT-B> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)  next(B)\n@@ -119,15 +113,13 @@ test_expect_success \"git-push -f ($PROTOCOL)\" '\n \t * [new branch] HEAD -> a/b/c\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/a/b/c\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/review/main/topic\n \t<TAG-v123> refs/tags/v123\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)  tags/v123  refs/review/main/topic(A)  a/b/c(A)\ndiff --git a/t/t5411/test-0001-standard-git-push--porcelain.sh b/t/t5411/test-0001-standard-git-push--porcelain.sh\nindex 59630ef7e5..bcbda72341 100644\n--- a/t/t5411/test-0001-standard-git-push--porcelain.sh\n+++ b/t/t5411/test-0001-standard-git-push--porcelain.sh\n@@ -20,13 +20,11 @@ test_expect_success \"git-push ($PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)  next(A)\n@@ -48,13 +46,11 @@ test_expect_success \"git-push --atomic ($PROTOCOL/porcelain)\" '\n \t!    <COMMIT-B>:refs/heads/next    [rejected] (atomic push failed)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)  next(A)\n@@ -80,13 +76,11 @@ test_expect_success \"non-fast-forward git-push ($PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \t<COMMIT-B> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)  next(B)\n@@ -123,15 +117,13 @@ test_expect_success \"git-push -f ($PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/a/b/c\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/review/main/topic\n \t<TAG-v123> refs/tags/v123\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)  tags/v123  refs/review/main/topic(A)  a/b/c(A)\ndiff --git a/t/t5411/test-0002-pre-receive-declined.sh b/t/t5411/test-0002-pre-receive-declined.sh\nindex c5c76bfe93..0c3490c9b1 100644\n--- a/t/t5411/test-0002-pre-receive-declined.sh\n+++ b/t/t5411/test-0002-pre-receive-declined.sh\n@@ -20,12 +20,10 @@ test_expect_success \"git-push is declined ($PROTOCOL)\" '\n \t ! [remote rejected] HEAD -> next (pre-receive hook declined)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"cleanup ($PROTOCOL)\" '\ndiff --git a/t/t5411/test-0003-pre-receive-declined--porcelain.sh b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\nindex 45050b642a..e9c9db5d1f 100644\n--- a/t/t5411/test-0003-pre-receive-declined--porcelain.sh\n+++ b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\n@@ -21,12 +21,10 @@ test_expect_success \"git-push is declined ($PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"cleanup ($PROTOCOL/porcelain)\" '\ndiff --git a/t/t5411/test-0011-no-hook-error.sh b/t/t5411/test-0011-no-hook-error.sh\nindex 77b45b0cbd..3ef136e6ef 100644\n--- a/t/t5411/test-0011-no-hook-error.sh\n+++ b/t/t5411/test-0011-no-hook-error.sh\n@@ -19,13 +19,11 @@ test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL)\n \t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             next(A)\n@@ -55,10 +53,8 @@ test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCO\n \t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0012-no-hook-error--porcelain.sh b/t/t5411/test-0012-no-hook-error--porcelain.sh\nindex 142f4835e4..19f66fbd7d 100644\n--- a/t/t5411/test-0012-no-hook-error--porcelain.sh\n+++ b/t/t5411/test-0012-no-hook-error--porcelain.sh\n@@ -20,13 +20,11 @@ test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL/\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             next(A)\n@@ -57,10 +55,8 @@ test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCO\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh\nindex bddface163..095e613f6f 100644\n--- a/t/t5411/test-0013-bad-protocol.sh\n+++ b/t/t5411/test-0013-bad-protocol.sh\n@@ -34,12 +34,9 @@ test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL)\" '\n \tEOF\n \ttest_cmp expect actual-error &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-read-version, $PROTOCOL)\" '\n@@ -68,12 +65,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-version, $PROTO\n \tgrep \"remote: fatal: die with the --die-read-version option\" out-$test_count &&\n \tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out-$test_count &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-write-version, $PROTOCOL)\" '\n@@ -102,12 +96,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-write-version, $PROT\n \tgrep \"remote: fatal: die with the --die-write-version option\" out-$test_count &&\n \tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out-$test_count &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-read-commands, $PROTOCOL)\" '\n@@ -135,12 +126,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-commands, $PROT\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-read-commands option\" out-$test_count &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-read-push-options, $PROTOCOL)\" '\n@@ -170,12 +158,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-push-options, $\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-read-push-options option\" out-$test_count &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-write-report, $PROTOCOL)\" '\n@@ -203,12 +188,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-write-report, $PROTO\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-write-report option\" out-$test_count &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (no report, $PROTOCOL)\" '\n@@ -240,13 +222,10 @@ test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL)\" '\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             next(A)\n@@ -284,12 +263,9 @@ test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL)\" '\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL)\" '\n@@ -320,10 +296,7 @@ test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL)\" '\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0014-bad-protocol--porcelain.sh b/t/t5411/test-0014-bad-protocol--porcelain.sh\nindex 64bfa27dcc..a44649789c 100644\n--- a/t/t5411/test-0014-bad-protocol--porcelain.sh\n+++ b/t/t5411/test-0014-bad-protocol--porcelain.sh\n@@ -34,12 +34,9 @@ test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL/porc\n \tEOF\n \ttest_cmp expect actual-error &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-read-version, $PROTOCOL/porcelain)\" '\n@@ -68,12 +65,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-version, $PROTO\n \tgrep \"remote: fatal: die with the --die-read-version option\" out-$test_count &&\n \tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out-$test_count &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-write-version, $PROTOCOL/porcelain)\" '\n@@ -102,12 +96,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-write-version, $PROT\n \tgrep \"remote: fatal: die with the --die-write-version option\" out-$test_count &&\n \tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out-$test_count &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-read-commands, $PROTOCOL/porcelain)\" '\n@@ -135,12 +126,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-commands, $PROT\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-read-commands option\" out-$test_count &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-read-push-options, $PROTOCOL/porcelain)\" '\n@@ -170,12 +158,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-push-options, $\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-read-push-options option\" out-$test_count &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (hook --die-write-report, $PROTOCOL/porcelain)\" '\n@@ -203,12 +188,9 @@ test_expect_success \"proc-receive: bad protocol (hook --die-write-report, $PROTO\n \ttest_cmp expect actual &&\n \tgrep \"remote: fatal: die with the --die-write-report option\" out-$test_count &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (no report, $PROTOCOL/porcelain)\" '\n@@ -241,13 +223,10 @@ test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL/porcelain)\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             next(A)\n@@ -285,12 +264,9 @@ test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)\" '\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL/porcelain)\" '\n@@ -322,10 +298,7 @@ test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL/porce\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0020-report-ng.sh b/t/t5411/test-0020-report-ng.sh\nindex 6618c91841..ad2c8f6535 100644\n--- a/t/t5411/test-0020-report-ng.sh\n+++ b/t/t5411/test-0020-report-ng.sh\n@@ -24,12 +24,10 @@ test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL)\" '\n \t ! [remote rejected] HEAD -> refs/for/main/topic (failed)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL)\" '\n@@ -58,10 +56,8 @@ test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL)\"\n \t ! [remote rejected] HEAD -> refs/for/main/topic (error msg)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0021-report-ng--porcelain.sh b/t/t5411/test-0021-report-ng--porcelain.sh\nindex 44ad1c7de4..d8ae9d3414 100644\n--- a/t/t5411/test-0021-report-ng--porcelain.sh\n+++ b/t/t5411/test-0021-report-ng--porcelain.sh\n@@ -25,12 +25,10 @@ test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL/por\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL/porcelain)\" '\n@@ -60,10 +58,8 @@ test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL/p\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0022-report-unexpect-ref.sh b/t/t5411/test-0022-report-unexpect-ref.sh\nindex b0c5a6e168..dbed467186 100644\n--- a/t/t5411/test-0022-report-unexpect-ref.sh\n+++ b/t/t5411/test-0022-report-unexpect-ref.sh\n@@ -30,12 +30,10 @@ test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL)\" '\n \t ! [remote rejected] HEAD -> refs/for/main/topic (proc-receive failed to report status)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)\ndiff --git a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\nindex 212927b8dc..e89096fa13 100644\n--- a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n+++ b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n@@ -31,12 +31,10 @@ test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL/porcelain)\"\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)\ndiff --git a/t/t5411/test-0024-report-unknown-ref.sh b/t/t5411/test-0024-report-unknown-ref.sh\nindex 94ffad55f2..77204244b8 100644\n--- a/t/t5411/test-0024-report-unknown-ref.sh\n+++ b/t/t5411/test-0024-report-unknown-ref.sh\n@@ -25,10 +25,8 @@ test_expect_success \"proc-receive: report unknown reference ($PROTOCOL)\" '\n \t ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (proc-receive failed to report status)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0025-report-unknown-ref--porcelain.sh b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\nindex 8ee7e66472..eeb1ce6b2c 100644\n--- a/t/t5411/test-0025-report-unknown-ref--porcelain.sh\n+++ b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\n@@ -26,10 +26,8 @@ test_expect_success \"proc-receive: report unknown reference ($PROTOCOL/porcelain\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh\nindex bb2cf59bdc..1ec2cb95bc 100644\n--- a/t/t5411/test-0026-push-options.sh\n+++ b/t/t5411/test-0026-push-options.sh\n@@ -20,12 +20,10 @@ test_expect_success \"proc-receive: not support push options ($PROTOCOL)\" '\n \tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n \t\tactual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"enable push options ($PROTOCOL)\" '\n@@ -69,13 +67,11 @@ test_expect_success \"proc-receive: ignore push-options for version 0 ($PROTOCOL)\n \t * [new reference] HEAD -> refs/for/main/topic\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"restore proc-receive hook ($PROTOCOL)\" '\n@@ -123,13 +119,11 @@ test_expect_success \"proc-receive: push with options ($PROTOCOL)\" '\n \t * [new reference] HEAD -> refs/for/main/topic\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             next(A)\ndiff --git a/t/t5411/test-0027-push-options--porcelain.sh b/t/t5411/test-0027-push-options--porcelain.sh\nindex 54e4f8f1d2..447fbfec0c 100644\n--- a/t/t5411/test-0027-push-options--porcelain.sh\n+++ b/t/t5411/test-0027-push-options--porcelain.sh\n@@ -21,12 +21,10 @@ test_expect_success \"proc-receive: not support push options ($PROTOCOL/porcelain\n \tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n \t\tactual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"enable push options ($PROTOCOL/porcelain)\" '\n@@ -72,13 +70,11 @@ test_expect_success \"proc-receive: ignore push-options for version 0 ($PROTOCOL/\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"restore proc-receive hook ($PROTOCOL/porcelain)\" '\n@@ -128,13 +124,11 @@ test_expect_success \"proc-receive: push with options ($PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/next\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             next(A)\ndiff --git a/t/t5411/test-0030-report-ok.sh b/t/t5411/test-0030-report-ok.sh\nindex 5d6feef118..8acb4f204f 100644\n--- a/t/t5411/test-0030-report-ok.sh\n+++ b/t/t5411/test-0030-report-ok.sh\n@@ -26,10 +26,8 @@ test_expect_success \"proc-receive: ok ($PROTOCOL)\" '\n \t * [new reference] HEAD -> refs/for/main/topic\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0031-report-ok--porcelain.sh b/t/t5411/test-0031-report-ok--porcelain.sh\nindex 91666d32df..a967718046 100644\n--- a/t/t5411/test-0031-report-ok--porcelain.sh\n+++ b/t/t5411/test-0031-report-ok--porcelain.sh\n@@ -27,10 +27,8 @@ test_expect_success \"proc-receive: ok ($PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nindex e6aa0f7018..437ade012d 100644\n--- a/t/t5411/test-0032-report-with-options.sh\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -247,10 +247,7 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL)\" '\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nindex a8ca5dd0dd..11486720ee 100644\n--- a/t/t5411/test-0033-report-with-options--porcelain.sh\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -256,10 +256,7 @@ test_expect_success \"proc-receive: report with multiple rewrites ($PROTOCOL/porc\n \tEOF\n \ttest_cmp expect actual &&\n \n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0034-report-ft.sh b/t/t5411/test-0034-report-ft.sh\nindex c355c290d2..6e0d08b327 100644\n--- a/t/t5411/test-0034-report-ft.sh\n+++ b/t/t5411/test-0034-report-ft.sh\n@@ -28,13 +28,11 @@ test_expect_success \"proc-receive: fall throught, let receive-pack to execute ($\n \t * [new reference] <COMMIT-B> -> refs/for/main/topic\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/for/main/topic\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             refs/for/main/topic(A)\ndiff --git a/t/t5411/test-0035-report-ft--porcelain.sh b/t/t5411/test-0035-report-ft--porcelain.sh\nindex 8ce4e58f2a..81bae9f2ec 100644\n--- a/t/t5411/test-0035-report-ft--porcelain.sh\n+++ b/t/t5411/test-0035-report-ft--porcelain.sh\n@@ -29,13 +29,11 @@ test_expect_success \"proc-receive: fall throught, let receive-pack to execute ($\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/for/main/topic\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             refs/for/main/topic(A)\ndiff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\nindex fad8eea8a0..be9b18b2b6 100644\n--- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh\n@@ -65,12 +65,10 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \t <OID-A>..<OID-B> HEAD -> refs/changes/25/125/1\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"proc-receive: check remote-tracking #1 ($PROTOCOL)\" '\n@@ -142,12 +140,10 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \t + <OID-B>...<OID-A> HEAD -> refs/changes/25/125/1 (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"proc-receive: check remote-tracking #2 ($PROTOCOL)\" '\n@@ -205,12 +201,10 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL)\" '\n \t <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"proc-receive: check remote-tracking #3 ($PROTOCOL)\" '\ndiff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\nindex dc254d57eb..95fb89c031 100644\n--- a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh\n@@ -51,12 +51,10 @@ test_expect_success \"proc-receive: multiple rewrite for one ref, no refname for\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL/porcelain)\" '\n@@ -114,12 +112,10 @@ test_expect_success \"proc-receive: multiple rewrites for one ref, no refname for\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL/porcelain)\" '\n@@ -163,10 +159,8 @@ test_expect_success \"proc-receive: multiple rewrites for one ref ($PROTOCOL/porc\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nindex 286f80d423..5e005299cc 100644\n--- a/t/t5411/test-0038-report-mixed-refs.sh\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -65,15 +65,13 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n \t ! [remote rejected] HEAD -> refs/for/next/topic3 (proc-receive failed to report status)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/bar\n \t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/foo\n \t<COMMIT-B> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)             foo(A)  bar(A))  baz(A)\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nindex 939b8a3b4f..8f891c5385 100644\n--- a/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -66,15 +66,13 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcel\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/bar\n \t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/foo\n \t<COMMIT-B> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(B)             foo(A)  bar(A))  baz(A)\ndiff --git a/t/t5411/test-0040-process-all-refs.sh b/t/t5411/test-0040-process-all-refs.sh\nindex 2565302a17..fdcdcc7c2e 100644\n--- a/t/t5411/test-0040-process-all-refs.sh\n+++ b/t/t5411/test-0040-process-all-refs.sh\n@@ -92,14 +92,12 @@ test_expect_success \"proc-receive: process all refs ($PROTOCOL)\" '\n \t + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/bar\n \t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             bar(A)  baz(B)\ndiff --git a/t/t5411/test-0041-process-all-refs--porcelain.sh b/t/t5411/test-0041-process-all-refs--porcelain.sh\nindex e21420b60d..73b35fe0aa 100644\n--- a/t/t5411/test-0041-process-all-refs--porcelain.sh\n+++ b/t/t5411/test-0041-process-all-refs--porcelain.sh\n@@ -93,14 +93,12 @@ test_expect_success \"proc-receive: process all refs ($PROTOCOL/porcelain)\" '\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-B> refs/heads/bar\n \t<COMMIT-A> refs/heads/baz\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)             bar(A)  baz(B)\ndiff --git a/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh b/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh\nindex 2e29518ec5..7214647ada 100644\n--- a/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh\n+++ b/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh\n@@ -50,12 +50,10 @@ test_expect_success \"proc-receive: update branch and new tag ($PROTOCOL)\" '\n \t * [new reference] v123 -> refs/pull/124/head\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \tEOF\n-\ttest_cmp expect actual\n '\n \n # Refs of upstream : main(A)\n@@ -63,14 +61,12 @@ test_expect_success \"proc-receive: update branch and new tag ($PROTOCOL)\" '\n test_expect_success \"setup upstream: create tags/v123 ($PROTOCOL)\" '\n \tgit -C \"$upstream\" update-ref refs/heads/topic $A &&\n \tgit -C \"$upstream\" update-ref refs/tags/v123 $TAG &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-A> refs/heads/topic\n \t<TAG-v123> refs/tags/v123\n \tEOF\n-\ttest_cmp expect actual\n '\n \n test_expect_success \"setup proc-receive hook ($PROTOCOL)\" '\n@@ -125,11 +121,9 @@ test_expect_success \"proc-receive: create/delete branch, and delete tag ($PROTOC\n \t * [new reference] <COMMIT-A> -> refs/pull/124/head\n \tEOF\n \ttest_cmp expect actual &&\n-\tgit -C \"$upstream\" show-ref >out &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n-\tcat >expect <<-EOF &&\n+\n+\ttest_cmp_refs -C \"$upstream\" <<-EOF\n \t<COMMIT-A> refs/heads/main\n \t<COMMIT-B> refs/heads/topic\n \tEOF\n-\ttest_cmp expect actual\n '\n-- \n2.28.0.15.gba9e81f0bd\n\n"},{"id":"414881","messageId":"20210121025331.21658-2-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20210120124906.GA8396@szeder.dev","subject":"[PATCH v3 1/2] t5411: use different out file to prevent overwriting","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2021-01-21T02:53:30Z","receivedAt":"2021-01-21T03:51:06Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nSZEDER reported that t5411 failed in Travis CI's s390x environment a\ncouple of times, and could be reproduced with '--stress' test on this\nspecific environment.  The test failure messages might look like this:\n\n    + test_cmp expect actual\n    --- expect      2021-01-17 21:55:23.430750004 +0000\n    +++ actual      2021-01-17 21:55:23.430750004 +0000\n    @@ -1 +1 @@\n    -<COMMIT-A> refs/heads/main\n    +<COMMIT-A> refs/heads/maifatal: the remote end hung up unexpectedly\n    error: last command exited with $?=1\n    not ok 86 - proc-receive: not support push options (builtin protocol)\n\nThe file 'actual' is filtered from the file 'out' which contains result\nof 'git show-ref' command.  Due to the error messages from other process\nis written into the file 'out' accidentally, t5411 failed.  SZEDER finds\nthe root cause of this issue:\n\n - 'git push' is executed with its standard output and error redirected\n   to the file 'out'.\n\n - 'git push' executes 'git receive-pack' internally, which inherits\n   the open file descriptors, so its output and error goes into that\n   same 'out' file.\n\n - 'git push' ends without waiting for the close of 'git-receive-pack'\n   for some cases, and the file 'out' is reused for test of\n   'git show-ref' afterwards.\n\n - A mixture of the output of 'git show-ref' abd 'git receive-pack'\n   leads to this issue.\n\nThe first intuitive reaction to resolve this issue is to remove the\nfile 'out' after use, so that the newly created file 'out' will have a\ndifferent file descriptor and will not be overwritten by the\n'git receive-pack' process.  But Johannes pointed out that removing an\nopen file is not possible on Windows.  So we use different temporary\nfile names to store the output of 'git push' to solve this issue.\n\nReported-by: SZEDER Gábor <szeder.dev@gmail.com>\nHelped-by: Johannes Sixt <j6t@kdbg.org>\nHelped-by: SZEDER Gábor <szeder.dev@gmail.com>\nSigned-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n---\n t/t5411/test-0000-standard-git-push.sh        |  8 +--\n .../test-0001-standard-git-push--porcelain.sh |  8 +--\n t/t5411/test-0002-pre-receive-declined.sh     |  4 +-\n ...st-0003-pre-receive-declined--porcelain.sh |  4 +-\n t/t5411/test-0011-no-hook-error.sh            |  8 +--\n t/t5411/test-0012-no-hook-error--porcelain.sh |  8 +--\n t/t5411/test-0013-bad-protocol.sh             | 50 +++++++++----------\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 50 +++++++++----------\n t/t5411/test-0020-report-ng.sh                |  8 +--\n t/t5411/test-0021-report-ng--porcelain.sh     |  8 +--\n t/t5411/test-0022-report-unexpect-ref.sh      |  4 +-\n ...est-0023-report-unexpect-ref--porcelain.sh |  4 +-\n t/t5411/test-0024-report-unknown-ref.sh       |  4 +-\n ...test-0025-report-unknown-ref--porcelain.sh |  4 +-\n t/t5411/test-0026-push-options.sh             |  4 +-\n t/t5411/test-0027-push-options--porcelain.sh  |  4 +-\n t/t5411/test-0032-report-with-options.sh      |  4 +-\n ...est-0033-report-with-options--porcelain.sh |  4 +-\n t/t5411/test-0038-report-mixed-refs.sh        |  4 +-\n .../test-0039-report-mixed-refs--porcelain.sh |  4 +-\n 20 files changed, 98 insertions(+), 98 deletions(-)\n\ndiff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh\nindex 47b058af7e..bdc4907191 100644\n--- a/t/t5411/test-0000-standard-git-push.sh\n+++ b/t/t5411/test-0000-standard-git-push.sh\n@@ -35,11 +35,11 @@ test_expect_success \"git-push --atomic ($PROTOCOL)\" '\n \ttest_must_fail git -C workbench push --atomic origin \\\n \t\tmain \\\n \t\t$B:refs/heads/next \\\n-\t\t>out 2>&1 &&\n+\t\t>out-$test_count 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; }\" \\\n \t\t-e \"/^ ! / { p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t ! [rejected] main -> main (non-fast-forward)\n@@ -65,8 +65,8 @@ test_expect_success \"non-fast-forward git-push ($PROTOCOL)\" '\n \t\tpush origin \\\n \t\tmain \\\n \t\t$B:refs/heads/next \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\ndiff --git a/t/t5411/test-0001-standard-git-push--porcelain.sh b/t/t5411/test-0001-standard-git-push--porcelain.sh\nindex bbead12bbb..59630ef7e5 100644\n--- a/t/t5411/test-0001-standard-git-push--porcelain.sh\n+++ b/t/t5411/test-0001-standard-git-push--porcelain.sh\n@@ -36,12 +36,12 @@ test_expect_success \"git-push --atomic ($PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --atomic --porcelain origin \\\n \t\tmain \\\n \t\t$B:refs/heads/next \\\n-\t\t>out 2>&1 &&\n+\t\t>out-$test_count 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"s/^# GETTEXT POISON #//\" \\\n \t\t-e \"/^To / { p; }\" \\\n \t\t-e \"/^! / { p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t!    refs/heads/main:refs/heads/main    [rejected] (non-fast-forward)\n@@ -67,8 +67,8 @@ test_expect_success \"non-fast-forward git-push ($PROTOCOL/porcelain)\" '\n \t\tpush --porcelain origin \\\n \t\tmain \\\n \t\t$B:refs/heads/next \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next\ndiff --git a/t/t5411/test-0002-pre-receive-declined.sh b/t/t5411/test-0002-pre-receive-declined.sh\nindex e7d113a158..c5c76bfe93 100644\n--- a/t/t5411/test-0002-pre-receive-declined.sh\n+++ b/t/t5411/test-0002-pre-receive-declined.sh\n@@ -12,8 +12,8 @@ test_expect_success \"git-push is declined ($PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\t$B:refs/heads/main \\\n \t\tHEAD:refs/heads/next \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t ! [remote rejected] <COMMIT-B> -> main (pre-receive hook declined)\ndiff --git a/t/t5411/test-0003-pre-receive-declined--porcelain.sh b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\nindex cc0cca6a47..45050b642a 100644\n--- a/t/t5411/test-0003-pre-receive-declined--porcelain.sh\n+++ b/t/t5411/test-0003-pre-receive-declined--porcelain.sh\n@@ -12,8 +12,8 @@ test_expect_success \"git-push is declined ($PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\t$B:refs/heads/main \\\n \t\tHEAD:refs/heads/next \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t!    <COMMIT-B>:refs/heads/main    [remote rejected] (pre-receive hook declined)\ndiff --git a/t/t5411/test-0011-no-hook-error.sh b/t/t5411/test-0011-no-hook-error.sh\nindex c50830982f..77b45b0cbd 100644\n--- a/t/t5411/test-0011-no-hook-error.sh\n+++ b/t/t5411/test-0011-no-hook-error.sh\n@@ -5,8 +5,8 @@ test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL)\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:next \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n@@ -41,8 +41,8 @@ test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCO\n \ttest_must_fail git -C workbench push --atomic origin \\\n \t\t$B:main \\\n \t\tHEAD:next \\\n-\t\tHEAD:refs/for/main/topic >out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tHEAD:refs/for/main/topic >out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main\ndiff --git a/t/t5411/test-0012-no-hook-error--porcelain.sh b/t/t5411/test-0012-no-hook-error--porcelain.sh\nindex 14ea433481..142f4835e4 100644\n--- a/t/t5411/test-0012-no-hook-error--porcelain.sh\n+++ b/t/t5411/test-0012-no-hook-error--porcelain.sh\n@@ -5,8 +5,8 @@ test_expect_success \"proc-receive: no hook, fail to push special ref ($PROTOCOL/\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:next \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n@@ -42,8 +42,8 @@ test_expect_success \"proc-receive: no hook, all failed for atomic push ($PROTOCO\n \ttest_must_fail git -C workbench push --porcelain --atomic origin \\\n \t\t$B:main \\\n \t\tHEAD:next \\\n-\t\tHEAD:refs/for/main/topic >out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tHEAD:refs/for/main/topic >out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main\ndiff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh\nindex b9be12be77..bddface163 100644\n--- a/t/t5411/test-0013-bad-protocol.sh\n+++ b/t/t5411/test-0013-bad-protocol.sh\n@@ -11,8 +11,8 @@ test_expect_success \"setup proc-receive hook (unknown version, $PROTOCOL)\" '\n test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \n \t# Check status report for git-push\n \tsed -n \\\n@@ -55,18 +55,18 @@ test_expect_success \"setup proc-receive hook (hook --die-read-version, $PROTOCOL\n test_expect_success \"proc-receive: bad protocol (hook --die-read-version, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n+\t\t>out-$test_count 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; }\" \\\n \t\t-e \"/^ ! / { p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgrep \"remote: fatal: die with the --die-read-version option\" out &&\n-\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out &&\n+\tgrep \"remote: fatal: die with the --die-read-version option\" out-$test_count &&\n+\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out-$test_count &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -89,18 +89,18 @@ test_expect_success \"setup proc-receive hook (hook --die-write-version, $PROTOCO\n test_expect_success \"proc-receive: bad protocol (hook --die-write-version, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n+\t\t>out-$test_count 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; }\" \\\n \t\t-e \"/^ ! / { p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgrep \"remote: fatal: die with the --die-write-version option\" out &&\n-\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out &&\n+\tgrep \"remote: fatal: die with the --die-write-version option\" out-$test_count &&\n+\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out-$test_count &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -123,17 +123,17 @@ test_expect_success \"setup proc-receive hook (hook --die-read-commands, $PROTOCO\n test_expect_success \"proc-receive: bad protocol (hook --die-read-commands, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n+\t\t>out-$test_count 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; }\" \\\n \t\t-e \"/^ ! / { p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgrep \"remote: fatal: die with the --die-read-commands option\" out &&\n+\tgrep \"remote: fatal: die with the --die-read-commands option\" out-$test_count &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -158,17 +158,17 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-push-options, $\n \ttest_must_fail git -C workbench push origin \\\n \t\t-o reviewers=user1,user2 \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n+\t\t>out-$test_count 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; }\" \\\n \t\t-e \"/^ ! / { p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgrep \"remote: fatal: die with the --die-read-push-options option\" out &&\n+\tgrep \"remote: fatal: die with the --die-read-push-options option\" out-$test_count &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -191,17 +191,17 @@ test_expect_success \"setup proc-receive hook (hook --die-write-report, $PROTOCOL\n test_expect_success \"proc-receive: bad protocol (hook --die-write-report, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n+\t\t>out-$test_count 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; }\" \\\n \t\t-e \"/^ ! / { p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)\n \tEOF\n \ttest_cmp expect actual &&\n-\tgrep \"remote: fatal: die with the --die-write-report option\" out &&\n+\tgrep \"remote: fatal: die with the --die-write-report option\" out-$test_count &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -224,8 +224,8 @@ test_expect_success \"setup proc-receive hook (no report, $PROTOCOL)\" '\n test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/heads/next \\\n-\t\tHEAD:refs/for/main/topic >out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tHEAD:refs/for/main/topic >out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n@@ -270,8 +270,8 @@ test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL)\" '\n test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/main/topic\\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n@@ -306,8 +306,8 @@ test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL)\" '\n test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\t\tHEAD:refs/for/main/topic \\\n-\t\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\ndiff --git a/t/t5411/test-0014-bad-protocol--porcelain.sh b/t/t5411/test-0014-bad-protocol--porcelain.sh\nindex fdb4569109..64bfa27dcc 100644\n--- a/t/t5411/test-0014-bad-protocol--porcelain.sh\n+++ b/t/t5411/test-0014-bad-protocol--porcelain.sh\n@@ -11,8 +11,8 @@ test_expect_success \"setup proc-receive hook (unknown version, $PROTOCOL/porcela\n test_expect_success \"proc-receive: bad protocol (unknown version, $PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \n \t# Check status report for git-push\n \tsed -n \\\n@@ -55,18 +55,18 @@ test_expect_success \"setup proc-receive hook (hook --die-read-version, $PROTOCOL\n test_expect_success \"proc-receive: bad protocol (hook --die-read-version, $PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n+\t\t>out-$test_count 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; n; p; n; p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgrep \"remote: fatal: die with the --die-read-version option\" out &&\n-\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out &&\n+\tgrep \"remote: fatal: die with the --die-read-version option\" out-$test_count &&\n+\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out-$test_count &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -89,18 +89,18 @@ test_expect_success \"setup proc-receive hook (hook --die-write-version, $PROTOCO\n test_expect_success \"proc-receive: bad protocol (hook --die-write-version, $PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n+\t\t>out-$test_count 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; n; p; n; p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgrep \"remote: fatal: die with the --die-write-version option\" out &&\n-\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out &&\n+\tgrep \"remote: fatal: die with the --die-write-version option\" out-$test_count &&\n+\tgrep \"remote: error: fail to negotiate version with proc-receive hook\" out-$test_count &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -123,17 +123,17 @@ test_expect_success \"setup proc-receive hook (hook --die-read-commands, $PROTOCO\n test_expect_success \"proc-receive: bad protocol (hook --die-read-commands, $PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n+\t\t>out-$test_count 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; n; p; n; p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgrep \"remote: fatal: die with the --die-read-commands option\" out &&\n+\tgrep \"remote: fatal: die with the --die-read-commands option\" out-$test_count &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -158,17 +158,17 @@ test_expect_success \"proc-receive: bad protocol (hook --die-read-push-options, $\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\t-o reviewers=user1,user2 \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n+\t\t>out-$test_count 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; n; p; n; p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgrep \"remote: fatal: die with the --die-read-push-options option\" out &&\n+\tgrep \"remote: fatal: die with the --die-read-push-options option\" out-$test_count &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -191,17 +191,17 @@ test_expect_success \"setup proc-receive hook (hook --die-write-report, $PROTOCOL\n test_expect_success \"proc-receive: bad protocol (hook --die-write-report, $PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n+\t\t>out-$test_count 2>&1 &&\n \tfilter_out_user_friendly_and_stable_output \\\n \t\t-e \"/^To / { p; n; p; n; p; }\" \\\n-\t\t<out >actual &&\n+\t\t<out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tTo <URL/of/upstream.git>\n \t!    HEAD:refs/for/main/topic    [remote rejected] (fail to run proc-receive hook)\n \tDone\n \tEOF\n \ttest_cmp expect actual &&\n-\tgrep \"remote: fatal: die with the --die-write-report option\" out &&\n+\tgrep \"remote: fatal: die with the --die-write-report option\" out-$test_count &&\n \n \tgit -C \"$upstream\" show-ref >out &&\n \tmake_user_friendly_and_stable_output <out >actual &&\n@@ -224,8 +224,8 @@ test_expect_success \"setup proc-receive hook (no report, $PROTOCOL/porcelain)\" '\n test_expect_success \"proc-receive: bad protocol (no report, $PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/heads/next \\\n-\t\tHEAD:refs/for/main/topic >out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\tHEAD:refs/for/main/topic >out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next\n@@ -270,8 +270,8 @@ test_expect_success \"setup proc-receive hook (no ref, $PROTOCOL/porcelain)\" '\n test_expect_success \"proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/for/main/topic\\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n@@ -307,8 +307,8 @@ test_expect_success \"setup proc-receive hook (unknown status, $PROTOCOL/porcelai\n test_expect_success \"proc-receive: bad protocol (unknown status, $PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\t\tHEAD:refs/for/main/topic \\\n-\t\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\ndiff --git a/t/t5411/test-0020-report-ng.sh b/t/t5411/test-0020-report-ng.sh\nindex 5a9e0daf2d..6618c91841 100644\n--- a/t/t5411/test-0020-report-ng.sh\n+++ b/t/t5411/test-0020-report-ng.sh\n@@ -12,8 +12,8 @@ test_expect_success \"setup proc-receive hook (ng, no message, $PROTOCOL)\" '\n test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n@@ -46,8 +46,8 @@ test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL)\" '\n test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\ndiff --git a/t/t5411/test-0021-report-ng--porcelain.sh b/t/t5411/test-0021-report-ng--porcelain.sh\nindex 93475a83cf..44ad1c7de4 100644\n--- a/t/t5411/test-0021-report-ng--porcelain.sh\n+++ b/t/t5411/test-0021-report-ng--porcelain.sh\n@@ -12,8 +12,8 @@ test_expect_success \"setup proc-receive hook (ng, no message, $PROTOCOL/porcelai\n test_expect_success \"proc-receive: fail to update (ng, no message, $PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\n@@ -47,8 +47,8 @@ test_expect_success \"setup proc-receive hook (ng message, $PROTOCOL/porcelain)\"\n test_expect_success \"proc-receive: fail to update (ng, with message, $PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\ndiff --git a/t/t5411/test-0022-report-unexpect-ref.sh b/t/t5411/test-0022-report-unexpect-ref.sh\nindex f8be8a0ba1..b0c5a6e168 100644\n--- a/t/t5411/test-0022-report-unexpect-ref.sh\n+++ b/t/t5411/test-0022-report-unexpect-ref.sh\n@@ -13,8 +13,8 @@ test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\t$B:refs/heads/main \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main\ndiff --git a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\nindex 778150fa03..212927b8dc 100644\n--- a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n+++ b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh\n@@ -13,8 +13,8 @@ test_expect_success \"proc-receive: report unexpected ref ($PROTOCOL/porcelain)\"\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\t$B:refs/heads/main \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main\ndiff --git a/t/t5411/test-0024-report-unknown-ref.sh b/t/t5411/test-0024-report-unknown-ref.sh\nindex d4e74e4681..94ffad55f2 100644\n--- a/t/t5411/test-0024-report-unknown-ref.sh\n+++ b/t/t5411/test-0024-report-unknown-ref.sh\n@@ -12,8 +12,8 @@ test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL)\" '\n test_expect_success \"proc-receive: report unknown reference ($PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/a/b/c/my/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\ndiff --git a/t/t5411/test-0025-report-unknown-ref--porcelain.sh b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\nindex 039e8b6163..8ee7e66472 100644\n--- a/t/t5411/test-0025-report-unknown-ref--porcelain.sh\n+++ b/t/t5411/test-0025-report-unknown-ref--porcelain.sh\n@@ -12,8 +12,8 @@ test_expect_success \"setup proc-receive hook (unexpected ref, $PROTOCOL/porcelai\n test_expect_success \"proc-receive: report unknown reference ($PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/for/a/b/c/my/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic\ndiff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh\nindex e88edb16a4..bb2cf59bdc 100644\n--- a/t/t5411/test-0026-push-options.sh\n+++ b/t/t5411/test-0026-push-options.sh\n@@ -16,8 +16,8 @@ test_expect_success \"proc-receive: not support push options ($PROTOCOL)\" '\n \t\t-o reviewer=user1 \\\n \t\torigin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n \t\tactual &&\n \tgit -C \"$upstream\" show-ref >out &&\ndiff --git a/t/t5411/test-0027-push-options--porcelain.sh b/t/t5411/test-0027-push-options--porcelain.sh\nindex 3a6561b5ea..54e4f8f1d2 100644\n--- a/t/t5411/test-0027-push-options--porcelain.sh\n+++ b/t/t5411/test-0027-push-options--porcelain.sh\n@@ -17,8 +17,8 @@ test_expect_success \"proc-receive: not support push options ($PROTOCOL/porcelain\n \t\t-o reviewer=user1 \\\n \t\torigin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \ttest_i18ngrep \"fatal: the receiving end does not support push options\" \\\n \t\tactual &&\n \tgit -C \"$upstream\" show-ref >out &&\ndiff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh\nindex a0faf5c7ff..e6aa0f7018 100644\n--- a/t/t5411/test-0032-report-with-options.sh\n+++ b/t/t5411/test-0032-report-with-options.sh\n@@ -13,8 +13,8 @@ test_expect_success \"setup proc-receive hook (option without matching ok, $PROTO\n test_expect_success \"proc-receive: report option without matching ok ($PROTOCOL)\" '\n \ttest_must_fail git -C workbench push origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\ndiff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh\nindex 32ae26bcfb..a8ca5dd0dd 100644\n--- a/t/t5411/test-0033-report-with-options--porcelain.sh\n+++ b/t/t5411/test-0033-report-with-options--porcelain.sh\n@@ -13,8 +13,8 @@ test_expect_success \"setup proc-receive hook (option without matching ok, $PROTO\n test_expect_success \"proc-receive: report option without matching ok ($PROTOCOL/porcelain)\" '\n \ttest_must_fail git -C workbench push --porcelain origin \\\n \t\tHEAD:refs/for/main/topic \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic\ndiff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh\nindex 0d071ebaa6..286f80d423 100644\n--- a/t/t5411/test-0038-report-mixed-refs.sh\n+++ b/t/t5411/test-0038-report-mixed-refs.sh\n@@ -24,8 +24,8 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL)\" '\n \t\tHEAD:refs/heads/foo \\\n \t\tHEAD:refs/for/main/topic \\\n \t\tHEAD:refs/for/next/topic3 \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main\ndiff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\nindex d8409912fd..939b8a3b4f 100644\n--- a/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh\n@@ -24,8 +24,8 @@ test_expect_success \"proc-receive: report update of mixed refs ($PROTOCOL/porcel\n \t\tHEAD:refs/heads/foo \\\n \t\tHEAD:refs/for/main/topic \\\n \t\tHEAD:refs/for/next/topic3 \\\n-\t\t>out 2>&1 &&\n-\tmake_user_friendly_and_stable_output <out >actual &&\n+\t\t>out-$test_count 2>&1 &&\n+\tmake_user_friendly_and_stable_output <out-$test_count >actual &&\n \tcat >expect <<-EOF &&\n \tremote: # pre-receive hook\n \tremote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main\n-- \n2.28.0.15.gba9e81f0bd\n\n"},{"id":"414884","messageId":"20210121025331.21658-1-worldhello.net@gmail.com","threadId":"52936","inReplyTo":"20210120124906.GA8396@szeder.dev","subject":"[PATCH v3 0/2] use unique out file in t5411","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2021-01-21T02:53:29Z","receivedAt":"2021-01-21T05:13:18Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n\nt5411 has some test cases for the behavior of failed 'git push'\ncommands.  Even the 'git push' command ended, the output file\nmay still be updated with messages from 'git-receive-pack' command\nrunning in background.  This breaks test cases which reuse the\nsame 'out' file.\n\n## Changes since v2\n\n+ Use unique 'out' file by appending the '-$test_count' suffix to\n  prevent accidental overwriting by internal 'git-receive-pack'\n  process.\n\n--\n\nJiang Xin (2):\n  t5411: use different out file to prevent overwriting\n  t5411: refactor check of refs using test_cmp_refs\n\n t/t5411/common-functions.sh                   | 15 +++\n t/t5411/once-0010-report-status-v1.sh         |  5 +-\n t/t5411/test-0000-standard-git-push.sh        | 32 +++----\n .../test-0001-standard-git-push--porcelain.sh | 32 +++----\n t/t5411/test-0002-pre-receive-declined.sh     | 10 +-\n ...st-0003-pre-receive-declined--porcelain.sh | 10 +-\n t/t5411/test-0011-no-hook-error.sh            | 20 ++--\n t/t5411/test-0012-no-hook-error--porcelain.sh | 20 ++--\n t/t5411/test-0013-bad-protocol.sh             | 95 +++++++------------\n t/t5411/test-0014-bad-protocol--porcelain.sh  | 95 +++++++------------\n t/t5411/test-0020-report-ng.sh                | 20 ++--\n t/t5411/test-0021-report-ng--porcelain.sh     | 20 ++--\n t/t5411/test-0022-report-unexpect-ref.sh      | 10 +-\n ...est-0023-report-unexpect-ref--porcelain.sh | 10 +-\n t/t5411/test-0024-report-unknown-ref.sh       | 10 +-\n ...test-0025-report-unknown-ref--porcelain.sh | 10 +-\n t/t5411/test-0026-push-options.sh             | 22 ++---\n t/t5411/test-0027-push-options--porcelain.sh  | 22 ++---\n t/t5411/test-0030-report-ok.sh                |  6 +-\n t/t5411/test-0031-report-ok--porcelain.sh     |  6 +-\n t/t5411/test-0032-report-with-options.sh      |  9 +-\n ...est-0033-report-with-options--porcelain.sh |  9 +-\n t/t5411/test-0034-report-ft.sh                |  6 +-\n t/t5411/test-0035-report-ft--porcelain.sh     |  6 +-\n ...t-0036-report-multi-rewrite-for-one-ref.sh | 18 ++--\n ...rt-multi-rewrite-for-one-ref--porcelain.sh | 18 ++--\n t/t5411/test-0038-report-mixed-refs.sh        | 10 +-\n .../test-0039-report-mixed-refs--porcelain.sh | 10 +-\n t/t5411/test-0040-process-all-refs.sh         |  6 +-\n .../test-0041-process-all-refs--porcelain.sh  |  6 +-\n ...t-0050-proc-receive-refs-with-modifiers.sh | 18 ++--\n 31 files changed, 224 insertions(+), 362 deletions(-)\n\n-- \n2.28.0.15.gba9e81f0bd\n\n"},{"id":"414886","messageId":"CANYiYbEKi=DD5-7Ss4KMF4zXNxGr2cABed--T-uwAzSf2KA-nw@mail.gmail.com","threadId":"52936","inReplyTo":"20210120122858.GZ8396@szeder.dev","subject":"Re: [PATCH v19 03/10] receive-pack: add new proc-receive hook","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2021-01-21T02:21:36Z","receivedAt":"2021-01-21T05:26:41Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"SZEDER Gábor <szeder.dev@gmail.com> 于2021年1月20日周三 下午8:29写道：\n>\n> On Mon, Jan 18, 2021 at 04:24:11PM +0800, Jiang Xin wrote:\n> > SZEDER Gábor <szeder.dev@gmail.com> 于2021年1月18日周一 上午6:21写道：\n> > >\n> > >\n> > > This patch added a whole lot of test cases, and two of them '86 -\n> > > proc-receive: not support push options (builtin protocol)' and '95 -\n> > > proc-receive: not support push options (builtin protocol/porcelain)'\n> > > are prone to rare failures.\n> > >\n> > > On Thu, Aug 27, 2020 at 11:45:44AM -0400, Jiang Xin wrote:\n> > > > diff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh\n> > > > new file mode 100644\n> > > > index 0000000000..d0c4da8b23\n> > > > --- /dev/null\n> > > > +++ b/t/t5411/test-0026-push-options.sh\n> > >\n> > > > +# Refs of upstream : master(A)\n> > > > +# Refs of workbench: master(A)  tags/v123\n> > > > +# git push -o ...  :                       refs/for/master/topic\n> > > > +test_expect_success \"proc-receive: not support push options ($PROTOCOL)\" '\n> > > > +     test_must_fail git -C workbench push \\\n> > > > +             -o issue=123 \\\n> > > > +             -o reviewer=user1 \\\n> > > > +             origin \\\n> > > > +             HEAD:refs/for/master/topic \\\n> > > > +             >out 2>&1 &&\n> > >\n> > > Three relevant things are happening here:\n> > >\n> > >   - 'git push' is executed with its standard output and error\n> > >     redirected to the file 'out'.\n> > >\n> > >   - 'git push' executes 'git receive-pack' internally, which inherits\n> > >     the open file descriptors, so its output and error goes into that\n> > >     same 'out' file.\n> > >\n> > >   - 'git push' is expected to fail when it finds out that the other\n> > >     side doesn't support push options, but it does so with a simple\n> > >     die() right away, without waiting for its child 'git receive-pack'\n> > >     process to finish.\n> > >\n> > > > +     make_user_friendly_and_stable_output <out >actual &&\n> > > > +     test_i18ngrep \"fatal: the receiving end does not support push options\" \\\n> > > > +             actual &&\n> > > > +     git -C \"$upstream\" show-ref >out &&\n> > >\n> > > Here the shell opens and truncates the file 'out' to write 'git\n> > > show-ref's output, i.e. it is still the same 'out' file that was used\n> > > earlier.\n> > >\n> > > Consequently, it is possible that 'git receive-pack' is still running,\n> > > its open file descriptors to 'out' are still valid, and its \"fatal:\n> > > the remote end hung up unexpectedly\" error message about the suddenly\n> > > disappeared 'git push' can partially overwrite the output from 'git\n> > > show-ref'.\n>\n>\n> > > I think these are the only two tests that can cause this racy\n> > > behavior: by instrumenting finish_command() I found that in all other\n> > > tests where 'git push' is expected to fail it errors out gracefully\n> > > and waits for its 'git receive-pack' child process.\n> >\n> > Atomic push may have the same problem.\n>\n> I don't think so, because send_pack() doesn't die() when a ref is\n> rejected in an atomic push, but returns, and lets its caller terminate\n> in an usual way, including waiting for 'git receive-pack'.\n\nI find many places where the client side will die() before closing the\nservice side gracefully:\n\n + In `transport_push()`, if fail to push a submodule, will die().\n + In `git_transport_push()`, will die() for an unimplemented v2 protocol.\n + In `send_pack()`, will die() if\n    the server side has an incompatible hash algorithm, or\n    the receiving end does not support --signed push, or\n    the receiving end does not support --atomic push, or\n    the receiving end does not support push options\n"},{"id":"414887","messageId":"CANYiYbFU=mFwotLMhE99+gB1KAz=v6i4QSBHSAR-5ZVOPeB=VQ@mail.gmail.com","threadId":"52936","inReplyTo":"20210120124906.GA8396@szeder.dev","subject":"Re: [PATCH v2 1/2] t5411: use different out file to prevent overwriting","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2021-01-21T01:59:36Z","receivedAt":"2021-01-21T05:46:00Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"SZEDER Gábor <szeder.dev@gmail.com> 于2021年1月20日周三 下午8:49写道：\n>\n> Would it be possible/desirable to die gracefully when the receiving\n> end doesn't support push options?\n\nIf want to die gracefully, we need to end the pack protocol by sending\na flush packet, close the connection. However, since we can easily fix\nthis test case, I think changes for git protocol is unnecessary.\n\n> > diff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh\n> > index 47b058af7e..b074417d4b 100644\n> > --- a/t/t5411/test-0000-standard-git-push.sh\n> > +++ b/t/t5411/test-0000-standard-git-push.sh\n> > @@ -35,11 +35,11 @@ test_expect_success \"git-push --atomic ($PROTOCOL)\" '\n> >       test_must_fail git -C workbench push --atomic origin \\\n> >               main \\\n> >               $B:refs/heads/next \\\n> > -             >out 2>&1 &&\n> > +             >out-0000-1 2>&1 &&\n> >       filter_out_user_friendly_and_stable_output \\\n> >               -e \"/^To / { p; }\" \\\n> >               -e \"/^ ! / { p; }\" \\\n> > -             <out >actual &&\n> > +             <out-0000-1 >actual &&\n> >       cat >expect <<-EOF &&\n> >       To <URL/of/upstream.git>\n> >        ! [rejected] main -> main (non-fast-forward)\n> > @@ -65,8 +65,8 @@ test_expect_success \"non-fast-forward git-push ($PROTOCOL)\" '\n> >               push origin \\\n> >               main \\\n> >               $B:refs/heads/next \\\n> > -             >out 2>&1 &&\n> > -     make_user_friendly_and_stable_output <out >actual &&\n> > +             >out-0000-2 2>&1 &&\n> > +     make_user_friendly_and_stable_output <out-0000-2 >actual &&\n>\n> There will be a lot of sequential numbers this way, which might lead\n> to additional churn in the future, if we ever were to add more tests\n> somewhere in the middle.  However, our test framework does already\n> have a counter for test cases, so we could perhaps use that, i.e.\n> 'out-$test_count', to make sure that each test case has its own output\n> file.\n\nUsing $test_count is better, and will do.\n\nThanks.\n--\nJiang Xin\n"},{"id":"414892","messageId":"20210121061219.GF8396@szeder.dev","threadId":"52936","inReplyTo":"CANYiYbEKi=DD5-7Ss4KMF4zXNxGr2cABed--T-uwAzSf2KA-nw@mail.gmail.com","subject":"Re: [PATCH v19 03/10] receive-pack: add new proc-receive hook","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2021-01-21T06:12:19Z","receivedAt":"2021-01-21T06:14:11Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Thu, Jan 21, 2021 at 10:21:36AM +0800, Jiang Xin wrote:\n> SZEDER Gábor <szeder.dev@gmail.com> 于2021年1月20日周三 下午8:29写道：\n> >\n> > On Mon, Jan 18, 2021 at 04:24:11PM +0800, Jiang Xin wrote:\n> > > SZEDER Gábor <szeder.dev@gmail.com> 于2021年1月18日周一 上午6:21写道：\n> > > >\n> > > >\n> > > > This patch added a whole lot of test cases, and two of them '86 -\n> > > > proc-receive: not support push options (builtin protocol)' and '95 -\n> > > > proc-receive: not support push options (builtin protocol/porcelain)'\n> > > > are prone to rare failures.\n> > > >\n> > > > On Thu, Aug 27, 2020 at 11:45:44AM -0400, Jiang Xin wrote:\n> > > > > diff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh\n> > > > > new file mode 100644\n> > > > > index 0000000000..d0c4da8b23\n> > > > > --- /dev/null\n> > > > > +++ b/t/t5411/test-0026-push-options.sh\n> > > >\n> > > > > +# Refs of upstream : master(A)\n> > > > > +# Refs of workbench: master(A)  tags/v123\n> > > > > +# git push -o ...  :                       refs/for/master/topic\n> > > > > +test_expect_success \"proc-receive: not support push options ($PROTOCOL)\" '\n> > > > > +     test_must_fail git -C workbench push \\\n> > > > > +             -o issue=123 \\\n> > > > > +             -o reviewer=user1 \\\n> > > > > +             origin \\\n> > > > > +             HEAD:refs/for/master/topic \\\n> > > > > +             >out 2>&1 &&\n> > > >\n> > > > Three relevant things are happening here:\n> > > >\n> > > >   - 'git push' is executed with its standard output and error\n> > > >     redirected to the file 'out'.\n> > > >\n> > > >   - 'git push' executes 'git receive-pack' internally, which inherits\n> > > >     the open file descriptors, so its output and error goes into that\n> > > >     same 'out' file.\n> > > >\n> > > >   - 'git push' is expected to fail when it finds out that the other\n> > > >     side doesn't support push options, but it does so with a simple\n> > > >     die() right away, without waiting for its child 'git receive-pack'\n> > > >     process to finish.\n> > > >\n> > > > > +     make_user_friendly_and_stable_output <out >actual &&\n> > > > > +     test_i18ngrep \"fatal: the receiving end does not support push options\" \\\n> > > > > +             actual &&\n> > > > > +     git -C \"$upstream\" show-ref >out &&\n> > > >\n> > > > Here the shell opens and truncates the file 'out' to write 'git\n> > > > show-ref's output, i.e. it is still the same 'out' file that was used\n> > > > earlier.\n> > > >\n> > > > Consequently, it is possible that 'git receive-pack' is still running,\n> > > > its open file descriptors to 'out' are still valid, and its \"fatal:\n> > > > the remote end hung up unexpectedly\" error message about the suddenly\n> > > > disappeared 'git push' can partially overwrite the output from 'git\n> > > > show-ref'.\n> >\n> >\n> > > > I think these are the only two tests that can cause this racy\n> > > > behavior: by instrumenting finish_command() I found that in all other\n> > > > tests where 'git push' is expected to fail it errors out gracefully\n> > > > and waits for its 'git receive-pack' child process.\n> > >\n> > > Atomic push may have the same problem.\n> >\n> > I don't think so, because send_pack() doesn't die() when a ref is\n> > rejected in an atomic push, but returns, and lets its caller terminate\n> > in an usual way, including waiting for 'git receive-pack'.\n> \n> I find many places where the client side will die() before closing the\n> service side gracefully:\n> \n>  + In `transport_push()`, if fail to push a submodule, will die().\n>  + In `git_transport_push()`, will die() for an unimplemented v2 protocol.\n>  + In `send_pack()`, will die() if\n>     the server side has an incompatible hash algorithm, or\n>     the receiving end does not support --signed push, or\n>     the receiving end does not support --atomic push, or\n>     the receiving end does not support push options\n\nSure, but it seems that those are not covered in thet 5411.\n\nYou can build Git with the patch below, run t5411 with '-V -x', and\nthen look through all the cases to see where 'git push' waited or did\nnot wait for its 'git receive-pack' child process.\n\ndiff --git a/run-command.c b/run-command.c\nindex ea4d0fb4b1..4accdb343e 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -988,6 +988,7 @@ int start_command(struct child_process *cmd)\n int finish_command(struct child_process *cmd)\n {\n \tint ret = wait_or_whine(cmd->pid, cmd->argv[0], 0);\n+\tdprintf(3, \"finish_command(): '%s'\\n\", cmd->argv[0]);\n \ttrace2_child_exit(cmd, ret);\n \tchild_process_clear(cmd);\n \treturn ret;\n"},{"id":"416774","messageId":"xmqqft22w9v2.fsf@gitster.c.googlers.com","threadId":"52936","inReplyTo":"20210121025331.21658-1-worldhello.net@gmail.com","subject":"Re: [PATCH v3 0/2] use unique out file in t5411","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-11T21:52:17Z","receivedAt":"2021-02-11T21:53:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jiang Xin <worldhello.net@gmail.com> writes:\n\n> From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n>\n> t5411 has some test cases for the behavior of failed 'git push'\n> commands.  Even the 'git push' command ended, the output file\n> may still be updated with messages from 'git-receive-pack' command\n> running in background.  This breaks test cases which reuse the\n> same 'out' file.\n\nI do not recall hearing from SZEDER who raised concerns about \"a lot\nof churn that can be avoided by a change in the code to die more\ngracefully\" in his previous round's review.  This has been in 'seen'\nfor a long time, so let's move it down to 'next'.\n\nThanks.\n"},{"id":"416841","messageId":"CANYiYbHZsvGpVPUbtpm3fPWgnNaL32yUMk5B-rrm_NUkm9fXmg@mail.gmail.com","threadId":"52936","inReplyTo":"xmqqft22w9v2.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v3 0/2] use unique out file in t5411","fromName":"Jiang Xin","fromEmail":"worldhello.net@gmail.com","sentAt":"2021-02-13T15:13:08Z","receivedAt":"2021-02-13T15:14:19Z","isPatch":true,"sender":{"key":"worldhello.net@gmail.com","avatar":"https://avatars.githubusercontent.com/u/183860?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2021年2月12日周五 上午5:52写道：\n>\n> Jiang Xin <worldhello.net@gmail.com> writes:\n>\n> > From: Jiang Xin <zhiyou.jx@alibaba-inc.com>\n> >\n> > t5411 has some test cases for the behavior of failed 'git push'\n> > commands.  Even the 'git push' command ended, the output file\n> > may still be updated with messages from 'git-receive-pack' command\n> > running in background.  This breaks test cases which reuse the\n> > same 'out' file.\n>\n> I do not recall hearing from SZEDER who raised concerns about \"a lot\n> of churn that can be avoided by a change in the code to die more\n> gracefully\" in his previous round's review.  This has been in 'seen'\n> for a long time, so let's move it down to 'next'.\n>\n\nSZEDER's concerns about \"a lot of churn that can be avoided ...\" is in\nthis post:\n\n    https://public-inbox.org/git/20210120124906.GA8396@szeder.dev/\n\nI think current implementation of the client side of git-push is OK.\nIt's no necessary to add more code and more roundtrips for git-push\nclient side to complete the protocol in order to wait for end of the\nserver.\n\n--\nJiang Xin\n"}]}