{"thread":{"id":"54422","subject":"[PATCH] commit: use config-based hooks (config-based hooks part II)","startedAt":"2020-10-15T01:54:51Z","lastAt":"2021-03-10T22:37:32Z","messageCount":53,"participants":["Emily Shaffer","Junio C Hamano","Josh Steadmon"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"407566","messageId":"20201014232517.3068298-1-emilyshaffer@google.com","threadId":"54422","inReplyTo":null,"subject":"[PATCH] commit: use config-based hooks (config-based hooks part II)","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-10-14T23:25:17Z","receivedAt":"2020-10-15T01:54:51Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"As part of the adoption of config-based hooks, teach run_commit_hook()\nto call hook.h instead of run-command.h. This covers 'pre-commit',\n'commit-msg', and 'prepare-commit-msg'. Additionally, ask the hook\nlibrary - not run-command - whether any hooks will be run, as it's\npossible hooks may exist in the config but not the hookdir.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n\nThis is based on es/config-based-hooks (v5).\n\nSince config-based hooks v4, I split a new branch for the conversions of\nexisting hook callers. I'm hoping this will make it easier for me to\nwork on the architecture without needing to juggle these\nhopefully-slow-moving patches as frequently, and will reduce the load on\nreviewers.\n\nSince v4 also I removed the second patch changing the run_commit_hook()\nAPI. I had figured it might not be well received when I sent it, and it\nwasn't, so no worries.\n\nI'm hoping to have updates to this series pretty soon too with new\nconversions... so I guess this one may just be noise. Ah well.\n\n - Emily\n\n builtin/commit.c                                |  3 ++-\n builtin/merge.c                                 |  3 ++-\n commit.c                                        | 10 +++++++++-\n ...503-pre-commit-and-pre-merge-commit-hooks.sh | 17 +++++++++++++++--\n 4 files changed, 28 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 1dfd799ec5..a337ecd4c2 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -36,6 +36,7 @@\n #include \"help.h\"\n #include \"commit-reach.h\"\n #include \"commit-graph.h\"\n+#include \"hook.h\"\n \n static const char * const builtin_commit_usage[] = {\n \tN_(\"git commit [<options>] [--] <pathspec>...\"),\n@@ -983,7 +984,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \t\treturn 0;\n \t}\n \n-\tif (!no_verify && find_hook(\"pre-commit\")) {\n+\tif (!no_verify && hook_exists(\"pre-commit\", configured_hookdir_opt())) {\n \t\t/*\n \t\t * Re-read the index as pre-commit hook could have updated it,\n \t\t * and write it out as a tree.  We must do this before we invoke\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 9d5359edc2..36ba138f4e 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -41,6 +41,7 @@\n #include \"commit-reach.h\"\n #include \"wt-status.h\"\n #include \"commit-graph.h\"\n+#include \"hook.h\"\n \n #define DEFAULT_TWOHEAD (1<<0)\n #define DEFAULT_OCTOPUS (1<<1)\n@@ -829,7 +830,7 @@ static void prepare_to_commit(struct commit_list *remoteheads)\n \t * and write it out as a tree.  We must do this before we invoke\n \t * the editor and after we invoke run_status above.\n \t */\n-\tif (find_hook(\"pre-merge-commit\"))\n+\tif (hook_exists(\"pre-merge-commit\", configured_hookdir_opt()))\n \t\tdiscard_cache();\n \tread_cache_from(index_file);\n \tstrbuf_addbuf(&msg, &merge_msg);\ndiff --git a/commit.c b/commit.c\nindex f53429c0ac..b338f50103 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -21,6 +21,7 @@\n #include \"commit-reach.h\"\n #include \"run-command.h\"\n #include \"shallow.h\"\n+#include \"hook.h\"\n \n static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);\n \n@@ -1635,8 +1636,11 @@ int run_commit_hook(int editor_is_used, const char *index_file,\n {\n \tstruct strvec hook_env = STRVEC_INIT;\n \tva_list args;\n+\tconst char *arg;\n+\tstruct strvec hook_args = STRVEC_INIT;\n \tint ret;\n \n+\n \tstrvec_pushf(&hook_env, \"GIT_INDEX_FILE=%s\", index_file);\n \n \t/*\n@@ -1646,9 +1650,13 @@ int run_commit_hook(int editor_is_used, const char *index_file,\n \t\tstrvec_push(&hook_env, \"GIT_EDITOR=:\");\n \n \tva_start(args, name);\n-\tret = run_hook_ve(hook_env.v, name, args);\n+\twhile ((arg = va_arg(args, const char *)))\n+\t\tstrvec_push(&hook_args, arg);\n \tva_end(args);\n+\n+\tret = run_hooks(hook_env.v, name, &hook_args, configured_hookdir_opt());\n \tstrvec_clear(&hook_env);\n+\tstrvec_clear(&hook_args);\n \n \treturn ret;\n }\ndiff --git a/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh b/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh\nindex b3485450a2..fc93bc3d23 100755\n--- a/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh\n+++ b/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh\n@@ -5,8 +5,8 @@ test_description='pre-commit and pre-merge-commit hooks'\n . ./test-lib.sh\n \n HOOKDIR=\"$(git rev-parse --git-dir)/hooks\"\n-PRECOMMIT=\"$HOOKDIR/pre-commit\"\n-PREMERGE=\"$HOOKDIR/pre-merge-commit\"\n+PRECOMMIT=\"$(pwd)/$HOOKDIR/pre-commit\"\n+PREMERGE=\"$(pwd)/$HOOKDIR/pre-merge-commit\"\n \n # Prepare sample scripts that write their $0 to actual_hooks\n test_expect_success 'sample script setup' '\n@@ -103,6 +103,19 @@ test_expect_success 'with succeeding hook' '\n \ttest_cmp expected_hooks actual_hooks\n '\n \n+# NEEDSWORK: when 'git hook add' and 'git hook remove' have been added, use that\n+# instead\n+test_expect_success 'with succeeding hook (config-based)' '\n+\ttest_when_finished \"git config --unset hook.pre-commit.command success.sample\" &&\n+\ttest_when_finished \"rm -f expected_hooks actual_hooks\" &&\n+\tgit config hook.pre-commit.command \"$HOOKDIR/success.sample\" &&\n+\techo \"$HOOKDIR/success.sample\" >expected_hooks &&\n+\techo \"more\" >>file &&\n+\tgit add file &&\n+\tgit commit -m \"more\" &&\n+\ttest_cmp expected_hooks actual_hooks\n+'\n+\n test_expect_success 'with succeeding hook (merge)' '\n \ttest_when_finished \"rm -f \\\"$PREMERGE\\\" expected_hooks actual_hooks\" &&\n \tcp \"$HOOKDIR/success.sample\" \"$PREMERGE\" &&\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"407736","messageId":"xmqq4kmuc951.fsf@gitster.c.googlers.com","threadId":"54422","inReplyTo":"20201014232517.3068298-1-emilyshaffer@google.com","subject":"Re: [PATCH] commit: use config-based hooks (config-based hooks part II)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-16T18:34:34Z","receivedAt":"2020-10-16T18:34:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Emily Shaffer <emilyshaffer@google.com> writes:\n\n> @@ -983,7 +984,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n>  \t\treturn 0;\n>  \t}\n>  \n> -\tif (!no_verify && find_hook(\"pre-commit\")) {\n> +\tif (!no_verify && hook_exists(\"pre-commit\", configured_hookdir_opt())) {\n\nSo, the migration from find_hook(), which is the traditional \"only\non the filesystem in $GIT_DIR/hooks\", to hook_exists(), which knows\nthe ones defined in the configuration files, is the same as the\nprevious round's.  Understood.\n\n> diff --git a/commit.c b/commit.c\n> index f53429c0ac..b338f50103 100644\n> --- a/commit.c\n> +++ b/commit.c\n> @@ -21,6 +21,7 @@\n>  #include \"commit-reach.h\"\n>  #include \"run-command.h\"\n>  #include \"shallow.h\"\n> +#include \"hook.h\"\n>  \n>  static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);\n>  \n> @@ -1635,8 +1636,11 @@ int run_commit_hook(int editor_is_used, const char *index_file,\n>  {\n>  \tstruct strvec hook_env = STRVEC_INIT;\n>  \tva_list args;\n> +\tconst char *arg;\n> +\tstruct strvec hook_args = STRVEC_INIT;\n>  \tint ret;\n>  \n> +\n>  \tstrvec_pushf(&hook_env, \"GIT_INDEX_FILE=%s\", index_file);\n\nNoise.\n\n> @@ -1646,9 +1650,13 @@ int run_commit_hook(int editor_is_used, const char *index_file,\n>  \t\tstrvec_push(&hook_env, \"GIT_EDITOR=:\");\n>  \n>  \tva_start(args, name);\n> -\tret = run_hook_ve(hook_env.v, name, args);\n> +\twhile ((arg = va_arg(args, const char *)))\n> +\t\tstrvec_push(&hook_args, arg);\n>  \tva_end(args);\n> +\n> +\tret = run_hooks(hook_env.v, name, &hook_args, configured_hookdir_opt());\n\nAnd this is the calling convention change.  Generally speaking,\nrun_hook_ve() and friends (does it still have friends?) are on their\nway out, and run_hooks() will be the only thing we need to learn in\nthe future.\n\n> diff --git a/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh b/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh\n> index b3485450a2..fc93bc3d23 100755\n> --- a/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh\n> +++ b/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh\n> @@ -5,8 +5,8 @@ test_description='pre-commit and pre-merge-commit hooks'\n>  . ./test-lib.sh\n>  \n>  HOOKDIR=\"$(git rev-parse --git-dir)/hooks\"\n> -PRECOMMIT=\"$HOOKDIR/pre-commit\"\n> -PREMERGE=\"$HOOKDIR/pre-merge-commit\"\n> +PRECOMMIT=\"$(pwd)/$HOOKDIR/pre-commit\"\n> +PREMERGE=\"$(pwd)/$HOOKDIR/pre-merge-commit\"\n\nWhat makes this change necessary?  Are we going to chdir around or\nsomething?  Isn't the configuration file the ONLY thing that needs\nto know their full location?\n\nShould and does hook.*.command configuration honor ~ expansion?  I\nthink our $TRASH_DIRECTORY is our $HOME during tests, so \n\n\tgit config hook.foo.command \"~/.git/hooks/success.sample\"\n\nmight be a worthy thing to test.  Also, do we have a clear\ndefinition on what happens to a relative pathname specified for the\nhook commands, or is it left \"undefined--do not do that\"?  If the\nformer, that would also be worth testing.  I'd imagine that majority\nof hooks defined in ~/.gitconfig WILL point at full path so testing\nrelative path may not matter too much for that particular use case,\nbut for ones defined in .git/config, I suspect it would be most\nnatural to take it as relative to the root of the working tree.\n\nIt may be a good change in the longer term, but it felt surprising\nto see such a change that would affect 103-5=98 lines of existing\ntests was made centrally here, without any explanation in the\nproposed log message.\n\nThanks.\n\n>  # Prepare sample scripts that write their $0 to actual_hooks\n>  test_expect_success 'sample script setup' '\n> @@ -103,6 +103,19 @@ test_expect_success 'with succeeding hook' '\n>  \ttest_cmp expected_hooks actual_hooks\n>  '\n>  \n> +# NEEDSWORK: when 'git hook add' and 'git hook remove' have been added, use that\n> +# instead\n> +test_expect_success 'with succeeding hook (config-based)' '\n> +\ttest_when_finished \"git config --unset hook.pre-commit.command success.sample\" &&\n> +\ttest_when_finished \"rm -f expected_hooks actual_hooks\" &&\n> +\tgit config hook.pre-commit.command \"$HOOKDIR/success.sample\" &&\n> +\techo \"$HOOKDIR/success.sample\" >expected_hooks &&\n> +\techo \"more\" >>file &&\n> +\tgit add file &&\n> +\tgit commit -m \"more\" &&\n> +\ttest_cmp expected_hooks actual_hooks\n> +'\n> +\n>  test_expect_success 'with succeeding hook (merge)' '\n>  \ttest_when_finished \"rm -f \\\"$PREMERGE\\\" expected_hooks actual_hooks\" &&\n>  \tcp \"$HOOKDIR/success.sample\" \"$PREMERGE\" &&\n"},{"id":"411469","messageId":"20201205014945.1502660-1-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201014232517.3068298-1-emilyshaffer@google.com","subject":"[PATCH 00/17] use config-based hooks (config-based hooks part","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-05T01:49:28Z","receivedAt":"2020-12-05T01:50:33Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"As of rebasing today, these are all the hooks, converted to hook.h.\n\nI chose between parallel and serial execution based only on reading the docs -\nplease, please correct me if I'm wrong.\n\nMost of these are pretty straightforward but there are a couple outstanding ones\non the server side, which I think could use some extra attention.\n\nThanks!\n - Emily\n\nEmily Shaffer (17):\n  commit: use config-based hooks\n  am: convert applypatch hooks to use config\n  merge: use config-based hooks for post-merge hook\n  gc: use hook library for pre-auto-gc hook\n  rebase: teach pre-rebase to use hook.h\n  read-cache: convert post-index-change hook to use config\n  receive-pack: convert push-to-checkout hook to hook.h\n  git-p4: use 'git hook' to run hooks\n  hooks: convert 'post-checkout' hook to hook library\n  hook: convert 'post-rewrite' hook to config\n  transport: convert pre-push hook to use config\n  reference-transaction: look for hooks in config\n  receive-pack: convert 'update' hook to hook.h\n  proc-receive: acquire hook list from hook.h\n  post-update: use hook.h library\n  receive-pack: convert receive hooks to hook.h\n  run-command: stop thinking about hooks\n\n Documentation/githooks.txt                    |  45 +++\n builtin/am.c                                  |  30 +-\n builtin/checkout.c                            |  16 +-\n builtin/clone.c                               |   7 +-\n builtin/commit.c                              |  11 +-\n builtin/gc.c                                  |   4 +-\n builtin/merge.c                               |  14 +-\n builtin/rebase.c                              |   7 +-\n builtin/receive-pack.c                        | 326 ++++++++++--------\n builtin/worktree.c                            |  30 +-\n commit.c                                      |  20 +-\n commit.h                                      |   3 +-\n git-p4.py                                     |  70 +---\n hook.c                                        |  39 ++-\n read-cache.c                                  |  12 +-\n refs.c                                        |  33 +-\n reset.c                                       |  15 +-\n run-command.c                                 |  66 ----\n run-command.h                                 |  24 --\n sequencer.c                                   |  83 ++---\n t/t1416-ref-transaction-hooks.sh              |  12 +-\n t/t5411/test-0015-too-many-hooks-error.sh     |  47 +++\n ...3-pre-commit-and-pre-merge-commit-hooks.sh |  17 +-\n transport.c                                   |  55 +--\n 24 files changed, 492 insertions(+), 494 deletions(-)\n create mode 100644 t/t5411/test-0015-too-many-hooks-error.sh\n\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"411470","messageId":"20201205014945.1502660-2-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201205014945.1502660-1-emilyshaffer@google.com","subject":"[PATCH 01/17] commit: use config-based hooks","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-05T01:49:29Z","receivedAt":"2020-12-05T01:50:40Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"As part of the adoption of config-based hooks, teach run_commit_hook()\nto call hook.h instead of run-command.h. This covers 'pre-commit',\n'commit-msg', and 'prepare-commit-msg'. Additionally, ask the hook\nlibrary - not run-command - whether any hooks will be run, as it's\npossible hooks may exist in the config but not the hookdir.\n\nBecause all but 'post-commit' hooks are expected to make some state\nchange, force all but 'post-commit' hook to run in series. 'post-commit'\n\"is meant primarily for notification, and cannot affect the outcome of\n`git commit`,\" so it is fine to run in parallel.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt                    | 10 ++++++++++\n builtin/commit.c                              | 11 +++++-----\n builtin/merge.c                               |  9 +++++----\n commit.c                                      | 20 +++++++++++++------\n commit.h                                      |  3 ++-\n sequencer.c                                   |  7 ++++---\n ...3-pre-commit-and-pre-merge-commit-hooks.sh | 17 ++++++++++++++--\n 7 files changed, 56 insertions(+), 21 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex ffccfc7760..8b352be43f 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -103,6 +103,8 @@ The default 'pre-commit' hook, when enabled--and with the\n `hooks.allownonascii` config option unset or set to false--prevents\n the use of non-ASCII filenames.\n \n+Hooks executed during 'pre-commit' will not be parallelized.\n+\n pre-merge-commit\n ~~~~~~~~~~~~~~~~\n \n@@ -125,6 +127,8 @@ need to be resolved and the result committed separately (see\n linkgit:git-merge[1]). At that point, this hook will not be executed,\n but the 'pre-commit' hook will, if it is enabled.\n \n+Hooks executed during 'pre-merge-commit' will not be parallelized.\n+\n prepare-commit-msg\n ~~~~~~~~~~~~~~~~~~\n \n@@ -150,6 +154,8 @@ be used as replacement for pre-commit hook.\n The sample `prepare-commit-msg` hook that comes with Git removes the\n help message found in the commented portion of the commit template.\n \n+Hooks executed during 'prepare-commit-msg' will not be parallelized.\n+\n commit-msg\n ~~~~~~~~~~\n \n@@ -166,6 +172,8 @@ file.\n The default 'commit-msg' hook, when enabled, detects duplicate\n `Signed-off-by` trailers, and aborts the commit if one is found.\n \n+Hooks executed during 'commit-msg' will not be parallelized.\n+\n post-commit\n ~~~~~~~~~~~\n \n@@ -175,6 +183,8 @@ invoked after a commit is made.\n This hook is meant primarily for notification, and cannot affect\n the outcome of `git commit`.\n \n+Hooks executed during 'post-commit' will run in parallel by default.\n+\n pre-rebase\n ~~~~~~~~~~\n \ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 505fe60956..f4dea2b510 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -36,6 +36,7 @@\n #include \"help.h\"\n #include \"commit-reach.h\"\n #include \"commit-graph.h\"\n+#include \"hook.h\"\n \n static const char * const builtin_commit_usage[] = {\n \tN_(\"git commit [<options>] [--] <pathspec>...\"),\n@@ -699,7 +700,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \t/* This checks and barfs if author is badly specified */\n \tdetermine_author_info(author_ident);\n \n-\tif (!no_verify && run_commit_hook(use_editor, index_file, \"pre-commit\", NULL))\n+\tif (!no_verify && run_commit_hook(use_editor, 0, index_file, \"pre-commit\", NULL))\n \t\treturn 0;\n \n \tif (squash_message) {\n@@ -983,7 +984,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \t\treturn 0;\n \t}\n \n-\tif (!no_verify && find_hook(\"pre-commit\")) {\n+\tif (!no_verify && hook_exists(\"pre-commit\", configured_hookdir_opt())) {\n \t\t/*\n \t\t * Re-read the index as pre-commit hook could have updated it,\n \t\t * and write it out as a tree.  We must do this before we invoke\n@@ -998,7 +999,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \t\treturn 0;\n \t}\n \n-\tif (run_commit_hook(use_editor, index_file, \"prepare-commit-msg\",\n+\tif (run_commit_hook(use_editor, 0, index_file, \"prepare-commit-msg\",\n \t\t\t    git_path_commit_editmsg(), hook_arg1, hook_arg2, NULL))\n \t\treturn 0;\n \n@@ -1015,7 +1016,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \t}\n \n \tif (!no_verify &&\n-\t    run_commit_hook(use_editor, index_file, \"commit-msg\", git_path_commit_editmsg(), NULL)) {\n+\t    run_commit_hook(use_editor, 0, index_file, \"commit-msg\", git_path_commit_editmsg(), NULL)) {\n \t\treturn 0;\n \t}\n \n@@ -1701,7 +1702,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \n \trepo_rerere(the_repository, 0);\n \trun_auto_maintenance(quiet);\n-\trun_commit_hook(use_editor, get_index_file(), \"post-commit\", NULL);\n+\trun_commit_hook(use_editor, 1, get_index_file(), \"post-commit\", NULL);\n \tif (amend && !no_post_rewrite) {\n \t\tcommit_post_rewrite(the_repository, current_head, &oid);\n \t}\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 1cff730715..d654b6923c 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -42,6 +42,7 @@\n #include \"commit-reach.h\"\n #include \"wt-status.h\"\n #include \"commit-graph.h\"\n+#include \"hook.h\"\n \n #define DEFAULT_TWOHEAD (1<<0)\n #define DEFAULT_OCTOPUS (1<<1)\n@@ -836,14 +837,14 @@ static void prepare_to_commit(struct commit_list *remoteheads)\n \tstruct strbuf msg = STRBUF_INIT;\n \tconst char *index_file = get_index_file();\n \n-\tif (!no_verify && run_commit_hook(0 < option_edit, index_file, \"pre-merge-commit\", NULL))\n+\tif (!no_verify && run_commit_hook(0 < option_edit, 0, index_file, \"pre-merge-commit\", NULL))\n \t\tabort_commit(remoteheads, NULL);\n \t/*\n \t * Re-read the index as pre-merge-commit hook could have updated it,\n \t * and write it out as a tree.  We must do this before we invoke\n \t * the editor and after we invoke run_status above.\n \t */\n-\tif (find_hook(\"pre-merge-commit\"))\n+\tif (hook_exists(\"pre-merge-commit\", configured_hookdir_opt()))\n \t\tdiscard_cache();\n \tread_cache_from(index_file);\n \tstrbuf_addbuf(&msg, &merge_msg);\n@@ -864,7 +865,7 @@ static void prepare_to_commit(struct commit_list *remoteheads)\n \t\tappend_signoff(&msg, ignore_non_trailer(msg.buf, msg.len), 0);\n \twrite_merge_heads(remoteheads);\n \twrite_file_buf(git_path_merge_msg(the_repository), msg.buf, msg.len);\n-\tif (run_commit_hook(0 < option_edit, get_index_file(), \"prepare-commit-msg\",\n+\tif (run_commit_hook(0 < option_edit, 0, get_index_file(), \"prepare-commit-msg\",\n \t\t\t    git_path_merge_msg(the_repository), \"merge\", NULL))\n \t\tabort_commit(remoteheads, NULL);\n \tif (0 < option_edit) {\n@@ -872,7 +873,7 @@ static void prepare_to_commit(struct commit_list *remoteheads)\n \t\t\tabort_commit(remoteheads, NULL);\n \t}\n \n-\tif (!no_verify && run_commit_hook(0 < option_edit, get_index_file(),\n+\tif (!no_verify && run_commit_hook(0 < option_edit, 0, get_index_file(),\n \t\t\t\t\t  \"commit-msg\",\n \t\t\t\t\t  git_path_merge_msg(the_repository), NULL))\n \t\tabort_commit(remoteheads, NULL);\ndiff --git a/commit.c b/commit.c\nindex fe1fa3dc41..1bad721a20 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -21,6 +21,7 @@\n #include \"commit-reach.h\"\n #include \"run-command.h\"\n #include \"shallow.h\"\n+#include \"hook.h\"\n \n static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);\n \n@@ -1630,25 +1631,32 @@ size_t ignore_non_trailer(const char *buf, size_t len)\n \treturn boc ? len - boc : len - cutoff;\n }\n \n-int run_commit_hook(int editor_is_used, const char *index_file,\n+int run_commit_hook(int editor_is_used, int parallelize, const char *index_file,\n \t\t    const char *name, ...)\n {\n-\tstruct strvec hook_env = STRVEC_INIT;\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SYNC;\n \tva_list args;\n+\tconst char *arg;\n \tint ret;\n \n-\tstrvec_pushf(&hook_env, \"GIT_INDEX_FILE=%s\", index_file);\n+\tif (parallelize)\n+\t\topt.jobs = configured_hook_jobs();\n+\n+\tstrvec_pushf(&opt.env, \"GIT_INDEX_FILE=%s\", index_file);\n \n \t/*\n \t * Let the hook know that no editor will be launched.\n \t */\n \tif (!editor_is_used)\n-\t\tstrvec_push(&hook_env, \"GIT_EDITOR=:\");\n+\t\tstrvec_push(&opt.env, \"GIT_EDITOR=:\");\n \n \tva_start(args, name);\n-\tret = run_hook_ve(hook_env.v, name, args);\n+\twhile ((arg = va_arg(args, const char *)))\n+\t\tstrvec_push(&opt.args, arg);\n \tva_end(args);\n-\tstrvec_clear(&hook_env);\n+\n+\tret = run_hooks(name, &opt);\n+\trun_hooks_opt_clear(&opt);\n \n \treturn ret;\n }\ndiff --git a/commit.h b/commit.h\nindex 5467786c7b..dd33ead8e0 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -352,6 +352,7 @@ int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused)\n int compare_commits_by_gen_then_commit_date(const void *a_, const void *b_, void *unused);\n \n LAST_ARG_MUST_BE_NULL\n-int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...);\n+int run_commit_hook(int editor_is_used, int parallelize, const char *index_file,\n+\t\t    const char *name, ...);\n \n #endif /* COMMIT_H */\ndiff --git a/sequencer.c b/sequencer.c\nindex 8909a46770..5a98fd2fbc 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -34,6 +34,7 @@\n #include \"commit-reach.h\"\n #include \"rebase-interactive.h\"\n #include \"reset.h\"\n+#include \"hook.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -1203,7 +1204,7 @@ static int run_prepare_commit_msg_hook(struct repository *r,\n \t} else {\n \t\targ1 = \"message\";\n \t}\n-\tif (run_commit_hook(0, r->index_file, \"prepare-commit-msg\", name,\n+\tif (run_commit_hook(0, 0, r->index_file, \"prepare-commit-msg\", name,\n \t\t\t    arg1, arg2, NULL))\n \t\tret = error(_(\"'prepare-commit-msg' hook failed\"));\n \n@@ -1438,7 +1439,7 @@ static int try_to_commit(struct repository *r,\n \t\t}\n \t}\n \n-\tif (find_hook(\"prepare-commit-msg\")) {\n+\tif (hook_exists(\"prepare-commit-msg\", configured_hookdir_opt())) {\n \t\tres = run_prepare_commit_msg_hook(r, msg, hook_commit);\n \t\tif (res)\n \t\t\tgoto out;\n@@ -1528,7 +1529,7 @@ static int try_to_commit(struct repository *r,\n \t\tgoto out;\n \t}\n \n-\trun_commit_hook(0, r->index_file, \"post-commit\", NULL);\n+\trun_commit_hook(0, 1, r->index_file, \"post-commit\", NULL);\n \tif (flags & AMEND_MSG)\n \t\tcommit_post_rewrite(r, current_head, oid);\n \ndiff --git a/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh b/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh\nindex b3485450a2..fc93bc3d23 100755\n--- a/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh\n+++ b/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh\n@@ -5,8 +5,8 @@ test_description='pre-commit and pre-merge-commit hooks'\n . ./test-lib.sh\n \n HOOKDIR=\"$(git rev-parse --git-dir)/hooks\"\n-PRECOMMIT=\"$HOOKDIR/pre-commit\"\n-PREMERGE=\"$HOOKDIR/pre-merge-commit\"\n+PRECOMMIT=\"$(pwd)/$HOOKDIR/pre-commit\"\n+PREMERGE=\"$(pwd)/$HOOKDIR/pre-merge-commit\"\n \n # Prepare sample scripts that write their $0 to actual_hooks\n test_expect_success 'sample script setup' '\n@@ -103,6 +103,19 @@ test_expect_success 'with succeeding hook' '\n \ttest_cmp expected_hooks actual_hooks\n '\n \n+# NEEDSWORK: when 'git hook add' and 'git hook remove' have been added, use that\n+# instead\n+test_expect_success 'with succeeding hook (config-based)' '\n+\ttest_when_finished \"git config --unset hook.pre-commit.command success.sample\" &&\n+\ttest_when_finished \"rm -f expected_hooks actual_hooks\" &&\n+\tgit config hook.pre-commit.command \"$HOOKDIR/success.sample\" &&\n+\techo \"$HOOKDIR/success.sample\" >expected_hooks &&\n+\techo \"more\" >>file &&\n+\tgit add file &&\n+\tgit commit -m \"more\" &&\n+\ttest_cmp expected_hooks actual_hooks\n+'\n+\n test_expect_success 'with succeeding hook (merge)' '\n \ttest_when_finished \"rm -f \\\"$PREMERGE\\\" expected_hooks actual_hooks\" &&\n \tcp \"$HOOKDIR/success.sample\" \"$PREMERGE\" &&\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"411471","messageId":"20201205014945.1502660-4-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201205014945.1502660-1-emilyshaffer@google.com","subject":"[PATCH 03/17] merge: use config-based hooks for post-merge hook","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-05T01:49:31Z","receivedAt":"2020-12-05T01:50:44Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"Teach post-merge to use the hook.h library instead of the run-command.h\nlibrary to run hooks. This means that post-merge hooks can come from the\nconfig as well as from the hookdir. post-merge is invoked only from\nbuiltin/merge.c.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt | 2 ++\n builtin/merge.c            | 5 ++++-\n 2 files changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 0842cd812c..f6ddf1aa22 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -236,6 +236,8 @@ save and restore any form of metadata associated with the working tree\n (e.g.: permissions/ownership, ACLS, etc).  See contrib/hooks/setgitperms.perl\n for an example of how to do this.\n \n+Hooks executed during 'post-merge' will run in parallel by default.\n+\n pre-push\n ~~~~~~~~\n \ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex d654b6923c..717fbaa019 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -443,6 +443,7 @@ static void finish(struct commit *head_commit,\n \t\t   const struct object_id *new_head, const char *msg)\n {\n \tstruct strbuf reflog_message = STRBUF_INIT;\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;\n \tconst struct object_id *head = &head_commit->object.oid;\n \n \tif (!msg)\n@@ -484,7 +485,9 @@ static void finish(struct commit *head_commit,\n \t}\n \n \t/* Run a post-merge hook */\n-\trun_hook_le(NULL, \"post-merge\", squash ? \"1\" : \"0\", NULL);\n+\tstrvec_push(&opt.args, squash ? \"1\" : \"0\");\n+\trun_hooks(\"post-merge\", &opt);\n+\trun_hooks_opt_clear(&opt);\n \n \tapply_autostash(git_path_merge_autostash(the_repository));\n \tstrbuf_release(&reflog_message);\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"411472","messageId":"20201205014945.1502660-3-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201205014945.1502660-1-emilyshaffer@google.com","subject":"[PATCH 02/17] am: convert applypatch hooks to use config","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-05T01:49:30Z","receivedAt":"2020-12-05T01:50:44Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"Teach pre-applypatch, post-applypatch, and applypatch-msg to use the\nhook.h library instead of the run-command.h library. This enables use of\nhooks specified in the config, in addition to those in the hookdir.\nThese three hooks are called only by builtin/am.c.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt |  6 ++++++\n builtin/am.c               | 12 +++++++++---\n 2 files changed, 15 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 8b352be43f..0842cd812c 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -58,6 +58,8 @@ the message file.\n The default 'applypatch-msg' hook, when enabled, runs the\n 'commit-msg' hook, if the latter is enabled.\n \n+Hooks run during 'applypatch-msg' will not be parallelized.\n+\n pre-applypatch\n ~~~~~~~~~~~~~~\n \n@@ -73,6 +75,8 @@ make a commit if it does not pass certain test.\n The default 'pre-applypatch' hook, when enabled, runs the\n 'pre-commit' hook, if the latter is enabled.\n \n+Hooks run during 'pre-applypatch' will be run in parallel by default.\n+\n post-applypatch\n ~~~~~~~~~~~~~~~\n \n@@ -82,6 +86,8 @@ and is invoked after the patch is applied and a commit is made.\n This hook is meant primarily for notification, and cannot affect\n the outcome of `git am`.\n \n+Hooks run during 'post-applypatch' will be run in parallel by default.\n+\n pre-commit\n ~~~~~~~~~~\n \ndiff --git a/builtin/am.c b/builtin/am.c\nindex f22c73a05b..22d147bc19 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -33,6 +33,7 @@\n #include \"string-list.h\"\n #include \"packfile.h\"\n #include \"repository.h\"\n+#include \"hook.h\"\n \n /**\n  * Returns the length of the first line of msg.\n@@ -426,9 +427,12 @@ static void am_destroy(const struct am_state *state)\n static int run_applypatch_msg_hook(struct am_state *state)\n {\n \tint ret;\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SYNC;\n \n \tassert(state->msg);\n-\tret = run_hook_le(NULL, \"applypatch-msg\", am_path(state, \"final-commit\"), NULL);\n+\tstrvec_push(&opt.args, am_path(state, \"final-commit\"));\n+\tret = run_hooks(\"applypatch-msg\", &opt);\n+\trun_hooks_opt_clear(&opt);\n \n \tif (!ret) {\n \t\tFREE_AND_NULL(state->msg);\n@@ -1558,8 +1562,9 @@ static void do_commit(const struct am_state *state)\n \tstruct commit_list *parents = NULL;\n \tconst char *reflog_msg, *author, *committer = NULL;\n \tstruct strbuf sb = STRBUF_INIT;\n+\tstruct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_ASYNC;\n \n-\tif (run_hook_le(NULL, \"pre-applypatch\", NULL))\n+\tif (run_hooks(\"pre-applypatch\", &hook_opt))\n \t\texit(1);\n \n \tif (write_cache_as_tree(&tree, 0, NULL))\n@@ -1611,8 +1616,9 @@ static void do_commit(const struct am_state *state)\n \t\tfclose(fp);\n \t}\n \n-\trun_hook_le(NULL, \"post-applypatch\", NULL);\n+\trun_hooks(\"post-applypatch\", &hook_opt);\n \n+\trun_hooks_opt_clear(&hook_opt);\n \tstrbuf_release(&sb);\n }\n \n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"411473","messageId":"20201205014945.1502660-6-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201205014945.1502660-1-emilyshaffer@google.com","subject":"[PATCH 05/17] rebase: teach pre-rebase to use hook.h","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-05T01:49:33Z","receivedAt":"2020-12-05T01:50:58Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"By using hook.h instead of run-command.h to run hooks, pre-rebase hooks\ncan now be specified in the config as well as in the hookdir. pre-rebase\nis not called anywhere besides builtin/rebase.c.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt | 2 ++\n builtin/rebase.c           | 7 +++++--\n 2 files changed, 7 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex d74308dc20..5dc0690607 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -200,6 +200,8 @@ two parameters.  The first parameter is the upstream from which\n the series was forked.  The second parameter is the branch being\n rebased, and is not set when rebasing the current branch.\n \n+Hooks executed during 'pre-rebase' will run in parallel by default.\n+\n post-checkout\n ~~~~~~~~~~~~~\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 19c7b377aa..f61ca3e5af 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -28,6 +28,7 @@\n #include \"sequencer.h\"\n #include \"rebase-interactive.h\"\n #include \"reset.h\"\n+#include \"hook.h\"\n \n #define DEFAULT_REFLOG_ACTION \"rebase\"\n \n@@ -1312,6 +1313,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tchar *squash_onto_name = NULL;\n \tint reschedule_failed_exec = -1;\n \tint allow_preemptive_ff = 1;\n+\tstruct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_ASYNC;\n \tstruct option builtin_rebase_options[] = {\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n@@ -2024,9 +2026,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t}\n \n \t/* If a hook exists, give it a chance to interrupt*/\n+\tstrvec_pushl(&hook_opt.args, options.upstream_arg, argc ? argv[0] : NULL, NULL);\n \tif (!ok_to_skip_pre_rebase &&\n-\t    run_hook_le(NULL, \"pre-rebase\", options.upstream_arg,\n-\t\t\targc ? argv[0] : NULL, NULL))\n+\t    run_hooks(\"pre-rebase\", &hook_opt))\n \t\tdie(_(\"The pre-rebase hook refused to rebase.\"));\n \n \tif (options.flags & REBASE_DIFFSTAT) {\n@@ -2106,6 +2108,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tret = !!run_specific_rebase(&options, action);\n \n cleanup:\n+\trun_hooks_opt_clear(&hook_opt);\n \tstrbuf_release(&buf);\n \tstrbuf_release(&revisions);\n \tfree(options.head_name);\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"411474","messageId":"20201205014945.1502660-5-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201205014945.1502660-1-emilyshaffer@google.com","subject":"[PATCH 04/17] gc: use hook library for pre-auto-gc hook","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-05T01:49:32Z","receivedAt":"2020-12-05T01:50:59Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"Using the hook.h library instead of the run-command.h library to run\npre-auto-gc means that those hooks can be set up in config files, as\nwell as in the hookdir. pre-auto-gc is called only from builtin/gc.c.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt | 2 ++\n builtin/gc.c               | 4 +++-\n 2 files changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex f6ddf1aa22..d74308dc20 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -553,6 +553,8 @@ This hook is invoked by `git gc --auto` (see linkgit:git-gc[1]). It\n takes no parameter, and exiting with non-zero status from this script\n causes the `git gc --auto` to abort.\n \n+Hooks run during 'pre-auto-gc' will be run in parallel by default.\n+\n post-rewrite\n ~~~~~~~~~~~~\n \ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 3e8d76fd5a..b01b6eb5fd 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -32,6 +32,7 @@\n #include \"remote.h\"\n #include \"object-store.h\"\n #include \"exec-cmd.h\"\n+#include \"hook.h\"\n \n #define FAILED_RUN \"failed to run %s\"\n \n@@ -340,6 +341,7 @@ static void add_repack_incremental_option(void)\n \n static int need_to_gc(void)\n {\n+\tstruct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_ASYNC;\n \t/*\n \t * Setting gc.auto to 0 or negative can disable the\n \t * automatic gc.\n@@ -386,7 +388,7 @@ static int need_to_gc(void)\n \telse\n \t\treturn 0;\n \n-\tif (run_hook_le(NULL, \"pre-auto-gc\", NULL))\n+\tif (run_hooks(\"pre-auto-gc\", &hook_opt))\n \t\treturn 0;\n \treturn 1;\n }\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"411475","messageId":"20201205014945.1502660-16-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201205014945.1502660-1-emilyshaffer@google.com","subject":"[PATCH 15/17] post-update: use hook.h library","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-05T01:49:43Z","receivedAt":"2020-12-05T01:51:45Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"By using run_hooks() instead of run_hook_le(), 'post-update' hooks can\nbe specified in the config as well as the hookdir.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt |  2 ++\n builtin/receive-pack.c     | 26 +++++++-------------------\n 2 files changed, 9 insertions(+), 19 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 2c59c537f9..1412cd5846 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -498,6 +498,8 @@ Both standard output and standard error output are forwarded to\n `git send-pack` on the other end, so you can simply `echo` messages\n for the user.\n \n+Hooks run during 'post-update' will be run in parallel by default.\n+\n reference-transaction\n ~~~~~~~~~~~~~~~~~~~~~\n \ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex b929a3505c..66af00fd8c 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1678,33 +1678,21 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n static void run_update_post_hook(struct command *commands)\n {\n \tstruct command *cmd;\n-\tstruct child_process proc = CHILD_PROCESS_INIT;\n-\tconst char *hook;\n-\n-\thook = find_hook(\"post-update\");\n-\tif (!hook)\n-\t\treturn;\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n \t\tif (cmd->error_string || cmd->did_not_exist)\n \t\t\tcontinue;\n-\t\tif (!proc.args.nr)\n-\t\t\tstrvec_push(&proc.args, hook);\n-\t\tstrvec_push(&proc.args, cmd->ref_name);\n+\t\tstrvec_push(&opt.args, cmd->ref_name);\n \t}\n-\tif (!proc.args.nr)\n+\tif (!opt.args.nr)\n \t\treturn;\n \n-\tproc.no_stdin = 1;\n-\tproc.stdout_to_stderr = 1;\n-\tproc.err = use_sideband ? -1 : 0;\n-\tproc.trace2_hook_name = \"post-update\";\n+\tif (use_sideband)\n+\t\topt.consume_sideband = hook_output_to_sideband;\n \n-\tif (!start_command(&proc)) {\n-\t\tif (use_sideband)\n-\t\t\tcopy_to_sideband(proc.err, -1, NULL);\n-\t\tfinish_command(&proc);\n-\t}\n+\trun_hooks(\"post-update\", &opt);\n+\trun_hooks_opt_clear(&opt);\n }\n \n static void check_aliased_update_internal(struct command *cmd,\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"411476","messageId":"20201205014945.1502660-7-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201205014945.1502660-1-emilyshaffer@google.com","subject":"[PATCH 06/17] read-cache: convert post-index-change hook to use config","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-05T01:49:34Z","receivedAt":"2020-12-05T01:51:45Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"By using hook.h instead of run-command.h to run, post-index-change hooks\ncan now be specified in the config in addition to the hookdir.\npost-index-change is not run anywhere besides in read-cache.c.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt |  2 ++\n read-cache.c               | 12 +++++++++---\n 2 files changed, 11 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 5dc0690607..8249ecec5f 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -711,6 +711,8 @@ and \"0\" meaning they were not.\n Only one parameter should be set to \"1\" when the hook runs.  The hook\n running passing \"1\", \"1\" should not be possible.\n \n+Hooks run during 'post-index-change' will be run in parallel by default.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/read-cache.c b/read-cache.c\nindex ecf6f68994..dcfc080aaa 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -25,6 +25,7 @@\n #include \"fsmonitor.h\"\n #include \"thread-utils.h\"\n #include \"progress.h\"\n+#include \"hook.h\"\n \n /* Mask for the name length in ce_flags in the on-disk index */\n \n@@ -3052,6 +3053,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n \t\t\t\t unsigned flags)\n {\n \tint ret;\n+\tstruct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_ASYNC;\n \n \t/*\n \t * TODO trace2: replace \"the_repository\" with the actual repo instance\n@@ -3070,9 +3072,13 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n \telse\n \t\tret = close_lock_file_gently(lock);\n \n-\trun_hook_le(NULL, \"post-index-change\",\n-\t\t\tistate->updated_workdir ? \"1\" : \"0\",\n-\t\t\tistate->updated_skipworktree ? \"1\" : \"0\", NULL);\n+\tstrvec_pushl(&hook_opt.args,\n+\t\t     istate->updated_workdir ? \"1\" : \"0\",\n+\t\t     istate->updated_skipworktree ? \"1\" : \"0\",\n+\t\t     NULL);\n+\trun_hooks(\"post-index-change\", &hook_opt);\n+\trun_hooks_opt_clear(&hook_opt);\n+\n \tistate->updated_workdir = 0;\n \tistate->updated_skipworktree = 0;\n \n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"411477","messageId":"20201205014945.1502660-8-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201205014945.1502660-1-emilyshaffer@google.com","subject":"[PATCH 07/17] receive-pack: convert push-to-checkout hook to hook.h","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-05T01:49:35Z","receivedAt":"2020-12-05T01:51:45Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"By using hook.h instead of run-command.h to invoke push-to-checkout,\nhooks can now be specified in the config as well as in the hookdir.\npush-to-checkout is not called anywhere but in builtin/receive-pack.c.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt |  1 +\n builtin/receive-pack.c     | 15 +++++++++++----\n 2 files changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 8249ecec5f..8de512ee5d 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -547,6 +547,7 @@ that switches branches while\n keeping the local changes in the working tree that do not interfere\n with the difference between the branches.\n \n+Hooks executed during 'push-to-checkout' will not be parallelized.\n \n pre-auto-gc\n ~~~~~~~~~~~\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex f1f0f7bef6..d64a3076be 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -29,6 +29,7 @@\n #include \"commit-reach.h\"\n #include \"worktree.h\"\n #include \"shallow.h\"\n+#include \"hook.h\"\n \n static const char * const receive_pack_usage[] = {\n \tN_(\"git receive-pack <git-dir>\"),\n@@ -1427,12 +1428,18 @@ static const char *push_to_checkout(unsigned char *hash,\n \t\t\t\t    struct strvec *env,\n \t\t\t\t    const char *work_tree)\n {\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SYNC;\n+\n \tstrvec_pushf(env, \"GIT_WORK_TREE=%s\", absolute_path(work_tree));\n-\tif (run_hook_le(env->v, push_to_checkout_hook,\n-\t\t\thash_to_hex(hash), NULL))\n+\tstrvec_pushv(&opt.env, env->v);\n+\tstrvec_push(&opt.args, hash_to_hex(hash));\n+\tif (run_hooks(push_to_checkout_hook, &opt)) {\n+\t\trun_hooks_opt_clear(&opt);\n \t\treturn \"push-to-checkout hook declined\";\n-\telse\n+\t} else {\n+\t\trun_hooks_opt_clear(&opt);\n \t\treturn NULL;\n+\t}\n }\n \n static const char *update_worktree(unsigned char *sha1, const struct worktree *worktree)\n@@ -1456,7 +1463,7 @@ static const char *update_worktree(unsigned char *sha1, const struct worktree *w\n \n \tstrvec_pushf(&env, \"GIT_DIR=%s\", absolute_path(git_dir));\n \n-\tif (!find_hook(push_to_checkout_hook))\n+\tif (!hook_exists(push_to_checkout_hook, configured_hookdir_opt()))\n \t\tretval = push_to_deploy(sha1, &env, work_tree);\n \telse\n \t\tretval = push_to_checkout(sha1, &env, work_tree);\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"411478","messageId":"20201205014945.1502660-15-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201205014945.1502660-1-emilyshaffer@google.com","subject":"[PATCH 14/17] proc-receive: acquire hook list from hook.h","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-05T01:49:42Z","receivedAt":"2020-12-05T01:51:45Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"The proc-receive hook differs from most other hooks Git invokes because\nthe hook and the parent Git process engage in bidirectional\ncommunication via stdin/stdout. This bidirectional communication is\nunsuitable for multiple hooks, whether they are in series or in\nparallel, and is incompatible with run-command.h:run_processes_parallel:\n\n- The proc-receive hook is intended to modify the state of the Git repo.\n  From 'git help githooks':\n    This [proc-receive] hook is responsible for updating the relevant\n    references and reporting the results back to 'receive-pack'.\n  This prevents parallelization and implies, at least, specific ordering\n  of hook execution.\n- The proc-receive hook can reject a push by aborting early with an\n  error code. If a former hook ran through the entire push contents\n  successfully but a later hook rejects some of the push, the repo may\n  be left in a partially-updated (and corrupt) state.\n- The callback model of the run_processes_parallel() API is unsuited to\n  the current implementation of proc-receive, which loops through\n  \"send-receive-consider\" with the child process. proc-receive today\n  relies on stateful communication with the child process, which would be\n  unwieldy to implement with callbacks and saved state.\n- Additionally, run_processes_parallel() is designed to collate the\n  output of many child processes into a single output (stderr or callback),\n  and would require significant work to tell the caller which process sent\n  the output, and indeed to collect any output before the child process\n  has exited.\n\nSo, rather than using hook.h:run_hooks() to invoke the proc-receive\nhook, receive-pack.c can learn to ask hook.h:hook_list() for the\nlocation of a hook to run. This allows users to configure their\nproc-receive in a global config for all repos if they want, or a local\nconfig if they just don't want to use the hookdir. Because running more\nthan one proc-receive hook doesn't make sense from a repo state\nperspective, we can explicitly ban configuring more than one\nproc-receive hook at a time.\n\nIf a user wants to globally configure one proc-receive hook for most of\ntheir repos, but override that hook in a single repo, they should use\n'skip' to manually remove the global hook in their special repo:\n\n~/.gitconfig:\n[hook.proc-receive]\n  command = /usr/bin/usual-proc-receive\n\n~/special-repo/.git/config:\n[hookcmd./usr/bin/usual-proc-receive]\n  skip = true\n[hook.proc-receive]\n  command = /usr/bin/special-proc-receive\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt                |  4 ++\n builtin/receive-pack.c                    | 33 +++++++++++++++-\n t/t5411/test-0015-too-many-hooks-error.sh | 47 +++++++++++++++++++++++\n 3 files changed, 82 insertions(+), 2 deletions(-)\n create mode 100644 t/t5411/test-0015-too-many-hooks-error.sh\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 2b3a74f249..2c59c537f9 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -423,6 +423,10 @@ 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+It is forbidden to specify more than one hook for 'proc-receive'. If a\n+globally-configured 'proc-receive' must be overridden, use\n+'hookcmd.<global-hook>.skip = true' to ignore it.\n+\n [[post-receive]]\n post-receive\n ~~~~~~~~~~~~\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex cd79ad6bdc..b929a3505c 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1137,11 +1137,40 @@ static int run_proc_receive_hook(struct command *commands,\n \tint version = 0;\n \tint code;\n \n-\targv[0] = find_hook(\"proc-receive\");\n-\tif (!argv[0]) {\n+\tstruct strbuf hookname = STRBUF_INIT;\n+\tstruct hook *proc_receive = NULL;\n+\tstruct list_head *pos, *hooks;\n+\n+\tstrbuf_addstr(&hookname, \"proc-receive\");\n+\thooks = hook_list(&hookname);\n+\n+\tlist_for_each(pos, hooks) {\n+\t\tif (proc_receive) {\n+\t\t\trp_error(\"only one 'proc-receive' hook can be specified\");\n+\t\t\treturn -1;\n+\t\t}\n+\t\tproc_receive = list_entry(pos, struct hook, list);\n+\t\t/* check if the hookdir hook should be ignored */\n+\t\tif (proc_receive->from_hookdir) {\n+\t\t\tswitch (configured_hookdir_opt()) {\n+\t\t\tcase hookdir_interactive:\n+\t\t\tcase hookdir_no:\n+\t\t\t\tproc_receive = NULL;\n+\t\t\t\tbreak;\n+\t\t\tdefault:\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t}\n+\n+\tif (!proc_receive) {\n \t\trp_error(\"cannot find hook 'proc-receive'\");\n \t\treturn -1;\n \t}\n+\n+\n+\targv[0] = proc_receive->command.buf;\n \targv[1] = NULL;\n \n \tproc.argv = argv;\ndiff --git a/t/t5411/test-0015-too-many-hooks-error.sh b/t/t5411/test-0015-too-many-hooks-error.sh\nnew file mode 100644\nindex 0000000000..de07b27b88\n--- /dev/null\n+++ b/t/t5411/test-0015-too-many-hooks-error.sh\n@@ -0,0 +1,47 @@\n+test_expect_success \"setup too  many proc-receive hooks (ok, $PROTOCOL)\" '\n+\twrite_script \"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+\tgit -C \"$upstream\" config --add \"hook.proc-receive.command\" proc-receive &&\n+\tcp proc-receive \"$upstream/hooks/proc-receive\"\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: reject more than one configured hook\" '\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: only one \"proc-receive\" hook can be specified\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\" config --unset \"hook.proc-receive.command\" \"proc-receive\" &&\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"411479","messageId":"20201205014945.1502660-10-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201205014945.1502660-1-emilyshaffer@google.com","subject":"[PATCH 09/17] hooks: convert 'post-checkout' hook to hook library","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-05T01:49:37Z","receivedAt":"2020-12-05T01:51:45Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"By using the 'hook.h' library, 'post-checkout' hooks can now be\nspecified in the config as well as in the hook directory.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt |  2 ++\n builtin/checkout.c         | 16 +++++++++++-----\n builtin/clone.c            |  7 +++++--\n builtin/worktree.c         | 30 ++++++++++++++----------------\n reset.c                    | 15 +++++++++++----\n 5 files changed, 43 insertions(+), 27 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 8de512ee5d..14035ef725 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -224,6 +224,8 @@ This hook can be used to perform repository validity checks, auto-display\n differences from the previous HEAD if different, or set working dir metadata\n properties.\n \n+Hooks executed during 'post-checkout' will not be parallelized.\n+\n post-merge\n ~~~~~~~~~~\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 9b82119129..20966452b8 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -9,6 +9,7 @@\n #include \"config.h\"\n #include \"diff.h\"\n #include \"dir.h\"\n+#include \"hook.h\"\n #include \"ll-merge.h\"\n #include \"lockfile.h\"\n #include \"merge-recursive.h\"\n@@ -104,13 +105,18 @@ struct branch_info {\n static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,\n \t\t\t      int changed)\n {\n-\treturn run_hook_le(NULL, \"post-checkout\",\n-\t\t\t   oid_to_hex(old_commit ? &old_commit->object.oid : &null_oid),\n-\t\t\t   oid_to_hex(new_commit ? &new_commit->object.oid : &null_oid),\n-\t\t\t   changed ? \"1\" : \"0\", NULL);\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SYNC;\n+\tint rc;\n \t/* \"new_commit\" can be NULL when checking out from the index before\n \t   a commit exists. */\n-\n+\tstrvec_pushl(&opt.args,\n+\t\t     oid_to_hex(old_commit ? &old_commit->object.oid : &null_oid),\n+\t\t     oid_to_hex(new_commit ? &new_commit->object.oid : &null_oid),\n+\t\t     changed ? \"1\" : \"0\",\n+\t\t     NULL);\n+\trc = run_hooks(\"post-checkout\", &opt);\n+\trun_hooks_opt_clear(&opt);\n+\treturn rc;\n }\n \n static int update_some(const struct object_id *oid, struct strbuf *base,\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex a0841923cf..307336e576 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -32,6 +32,7 @@\n #include \"connected.h\"\n #include \"packfile.h\"\n #include \"list-objects-filter-options.h\"\n+#include \"hook.h\"\n \n /*\n  * Overall FIXMEs:\n@@ -771,6 +772,7 @@ static int checkout(int submodule_progress)\n \tstruct tree *tree;\n \tstruct tree_desc t;\n \tint err = 0;\n+\tstruct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_SYNC;\n \n \tif (option_no_checkout)\n \t\treturn 0;\n@@ -816,8 +818,9 @@ static int checkout(int submodule_progress)\n \tif (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))\n \t\tdie(_(\"unable to write new index file\"));\n \n-\terr |= run_hook_le(NULL, \"post-checkout\", oid_to_hex(&null_oid),\n-\t\t\t   oid_to_hex(&oid), \"1\", NULL);\n+\tstrvec_pushl(&hook_opt.args, oid_to_hex(&null_oid), oid_to_hex(&oid), \"1\", NULL);\n+\terr |= run_hooks(\"post-checkout\", &hook_opt);\n+\trun_hooks_opt_clear(&hook_opt);\n \n \tif (!err && (option_recurse_submodules.nr > 0)) {\n \t\tstruct strvec args = STRVEC_INIT;\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 197fd24a55..9a87c4c120 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -12,6 +12,7 @@\n #include \"submodule.h\"\n #include \"utf8.h\"\n #include \"worktree.h\"\n+#include \"hook.h\"\n \n static const char * const worktree_usage[] = {\n \tN_(\"git worktree add [<options>] <path> [<commit-ish>]\"),\n@@ -455,22 +456,19 @@ static int add_worktree(const char *path, const char *refname,\n \t * is_junk is cleared, but do return appropriate code when hook fails.\n \t */\n \tif (!ret && opts->checkout) {\n-\t\tconst char *hook = find_hook(\"post-checkout\");\n-\t\tif (hook) {\n-\t\t\tconst char *env[] = { \"GIT_DIR\", \"GIT_WORK_TREE\", NULL };\n-\t\t\tcp.git_cmd = 0;\n-\t\t\tcp.no_stdin = 1;\n-\t\t\tcp.stdout_to_stderr = 1;\n-\t\t\tcp.dir = path;\n-\t\t\tcp.env = env;\n-\t\t\tcp.argv = NULL;\n-\t\t\tcp.trace2_hook_name = \"post-checkout\";\n-\t\t\tstrvec_pushl(&cp.args, absolute_path(hook),\n-\t\t\t\t     oid_to_hex(&null_oid),\n-\t\t\t\t     oid_to_hex(&commit->object.oid),\n-\t\t\t\t     \"1\", NULL);\n-\t\t\tret = run_command(&cp);\n-\t\t}\n+\t\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SYNC;\n+\n+\t\tstrvec_pushl(&opt.env, \"GIT_DIR\", \"GIT_WORK_TREE\", NULL);\n+\t\tstrvec_pushl(&opt.args,\n+\t\t\t     oid_to_hex(&null_oid),\n+\t\t\t     oid_to_hex(&commit->object.oid),\n+\t\t\t     \"1\",\n+\t\t\t     NULL);\n+\t\topt.dir = path;\n+\n+\t\tret = run_hooks(\"post-checkout\", &opt);\n+\n+\t\trun_hooks_opt_clear(&opt);\n \t}\n \n \tstrvec_clear(&child_env);\ndiff --git a/reset.c b/reset.c\nindex 2f4fbd07c5..e6bfaf67e1 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -7,6 +7,7 @@\n #include \"tree-walk.h\"\n #include \"tree.h\"\n #include \"unpack-trees.h\"\n+#include \"hook.h\"\n \n int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \t       const char *switch_to_branch, unsigned flags,\n@@ -126,10 +127,16 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \t\t\tret = create_symref(\"HEAD\", switch_to_branch,\n \t\t\t\t\t    reflog_head);\n \t}\n-\tif (run_hook)\n-\t\trun_hook_le(NULL, \"post-checkout\",\n-\t\t\t    oid_to_hex(orig ? orig : &null_oid),\n-\t\t\t    oid_to_hex(oid), \"1\", NULL);\n+\tif (run_hook) {\n+\t\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SYNC;\n+\t\tstrvec_pushl(&opt.args,\n+\t\t\t     oid_to_hex(orig ? orig : &null_oid),\n+\t\t\t     oid_to_hex(oid),\n+\t\t\t     \"1\",\n+\t\t\t     NULL);\n+\t\trun_hooks(\"post-checkout\", &opt);\n+\t\trun_hooks_opt_clear(&opt);\n+\t}\n \n leave_reset_head:\n \tstrbuf_release(&msg);\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"411480","messageId":"20201205014945.1502660-13-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201205014945.1502660-1-emilyshaffer@google.com","subject":"[PATCH 12/17] reference-transaction: look for hooks in config","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-05T01:49:40Z","receivedAt":"2020-12-05T01:51:45Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"By using the hook.h library, reference-transaction hooks can be\nspecified in the config instead.\n\nThe expected output of the test is not fully updated to reflect the\nabsolute path of the hook called because the 'update' hook has not yet\nbeen converted to use hook.h.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt       |  2 ++\n refs.c                           | 33 +++++++-------------------------\n t/t1416-ref-transaction-hooks.sh |  8 ++++----\n 3 files changed, 13 insertions(+), 30 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 8f5524055b..3a35500132 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -521,6 +521,8 @@ The exit status of the hook is ignored for any state except for the\n cause the transaction to be aborted. The hook will not be called with\n \"aborted\" state in that case.\n \n+Hooks run during 'reference-transaction' will be run in parallel by default.\n+\n push-to-checkout\n ~~~~~~~~~~~~~~~~\n \ndiff --git a/refs.c b/refs.c\nindex 392f0bbf68..b67bca367e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -18,6 +18,7 @@\n #include \"strvec.h\"\n #include \"repository.h\"\n #include \"sigchain.h\"\n+#include \"hook.h\"\n \n /*\n  * List of all available backends\n@@ -1957,47 +1958,27 @@ int ref_update_reject_duplicates(struct string_list *refnames,\n static int run_transaction_hook(struct ref_transaction *transaction,\n \t\t\t\tconst char *state)\n {\n-\tstruct child_process proc = CHILD_PROCESS_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n-\tconst char *hook;\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;\n \tint ret = 0, i;\n \n-\thook = find_hook(\"reference-transaction\");\n-\tif (!hook)\n-\t\treturn ret;\n-\n-\tstrvec_pushl(&proc.args, hook, state, NULL);\n-\tproc.in = -1;\n-\tproc.stdout_to_stderr = 1;\n-\tproc.trace2_hook_name = \"reference-transaction\";\n-\n-\tret = start_command(&proc);\n-\tif (ret)\n-\t\treturn ret;\n-\n-\tsigchain_push(SIGPIPE, SIG_IGN);\n+\tstrvec_push(&opt.args, state);\n \n \tfor (i = 0; i < transaction->nr; i++) {\n \t\tstruct ref_update *update = transaction->updates[i];\n \n \t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addf(&buf, \"%s %s %s\\n\",\n+\t\tstrbuf_addf(&buf, \"%s %s %s\",\n \t\t\t    oid_to_hex(&update->old_oid),\n \t\t\t    oid_to_hex(&update->new_oid),\n \t\t\t    update->refname);\n-\n-\t\tif (write_in_full(proc.in, buf.buf, buf.len) < 0) {\n-\t\t\tif (errno != EPIPE)\n-\t\t\t\tret = -1;\n-\t\t\tbreak;\n-\t\t}\n+\t\tstring_list_append(&opt.str_stdin, buf.buf);\n \t}\n \n-\tclose(proc.in);\n-\tsigchain_pop(SIGPIPE);\n+\tret = run_hooks(\"reference-transaction\", &opt);\n+\trun_hooks_opt_clear(&opt);\n \tstrbuf_release(&buf);\n \n-\tret |= finish_command(&proc);\n \treturn ret;\n }\n \ndiff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh\nindex f6e741c6c0..17f11f5cb0 100755\n--- a/t/t1416-ref-transaction-hooks.sh\n+++ b/t/t1416-ref-transaction-hooks.sh\n@@ -122,11 +122,11 @@ test_expect_success 'interleaving hook calls succeed' '\n \n \tcat >expect <<-EOF &&\n \t\thooks/update refs/tags/PRE $ZERO_OID $PRE_OID\n-\t\thooks/reference-transaction prepared\n-\t\thooks/reference-transaction committed\n+\t\t$(pwd)/target-repo.git/hooks/reference-transaction prepared\n+\t\t$(pwd)/target-repo.git/hooks/reference-transaction committed\n \t\thooks/update refs/tags/POST $ZERO_OID $POST_OID\n-\t\thooks/reference-transaction prepared\n-\t\thooks/reference-transaction committed\n+\t\t$(pwd)/target-repo.git/hooks/reference-transaction prepared\n+\t\t$(pwd)/target-repo.git/hooks/reference-transaction committed\n \tEOF\n \n \tgit push ./target-repo.git PRE POST &&\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"411481","messageId":"20201205014945.1502660-12-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201205014945.1502660-1-emilyshaffer@google.com","subject":"[PATCH 11/17] transport: convert pre-push hook to use config","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-05T01:49:39Z","receivedAt":"2020-12-05T01:51:45Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"By using the hook.h:run_hooks API, pre-push hooks can be specified in\nthe config as well as in the hookdir.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt |  2 ++\n transport.c                | 55 +++++++++-----------------------------\n 2 files changed, 14 insertions(+), 43 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex db290984f6..8f5524055b 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -271,6 +271,8 @@ If this hook exits with a non-zero status, `git push` will abort without\n pushing anything.  Information about why the push is rejected may be sent\n to the user by writing to standard error.\n \n+Hooks executed during 'pre-push' will run in parallel by default.\n+\n [[pre-receive]]\n pre-receive\n ~~~~~~~~~~~\ndiff --git a/transport.c b/transport.c\nindex 47da955e4f..6c67bc0fea 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -22,6 +22,7 @@\n #include \"protocol.h\"\n #include \"object-store.h\"\n #include \"color.h\"\n+#include \"hook.h\"\n \n static int transport_use_color = -1;\n static char transport_colors[][COLOR_MAXLEN] = {\n@@ -1162,31 +1163,13 @@ static void die_with_unpushed_submodules(struct string_list *needs_pushing)\n static int run_pre_push_hook(struct transport *transport,\n \t\t\t     struct ref *remote_refs)\n {\n-\tint ret = 0, x;\n+\tint ret = 0;\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;\n+\tstruct strbuf tmp = STRBUF_INIT;\n \tstruct ref *r;\n-\tstruct child_process proc = CHILD_PROCESS_INIT;\n-\tstruct strbuf buf;\n-\tconst char *argv[4];\n-\n-\tif (!(argv[0] = find_hook(\"pre-push\")))\n-\t\treturn 0;\n-\n-\targv[1] = transport->remote->name;\n-\targv[2] = transport->url;\n-\targv[3] = NULL;\n-\n-\tproc.argv = argv;\n-\tproc.in = -1;\n-\tproc.trace2_hook_name = \"pre-push\";\n-\n-\tif (start_command(&proc)) {\n-\t\tfinish_command(&proc);\n-\t\treturn -1;\n-\t}\n \n-\tsigchain_push(SIGPIPE, SIG_IGN);\n-\n-\tstrbuf_init(&buf, 256);\n+\tstrvec_push(&opt.args, transport->remote->name);\n+\tstrvec_push(&opt.args, transport->url);\n \n \tfor (r = remote_refs; r; r = r->next) {\n \t\tif (!r->peer_ref) continue;\n@@ -1195,30 +1178,16 @@ static int run_pre_push_hook(struct transport *transport,\n \t\tif (r->status == REF_STATUS_REJECT_REMOTE_UPDATED) continue;\n \t\tif (r->status == REF_STATUS_UPTODATE) continue;\n \n-\t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addf( &buf, \"%s %s %s %s\\n\",\n+\t\tstrbuf_reset(&tmp);\n+\t\tstrbuf_addf(&tmp, \"%s %s %s %s\",\n \t\t\t r->peer_ref->name, oid_to_hex(&r->new_oid),\n \t\t\t r->name, oid_to_hex(&r->old_oid));\n-\n-\t\tif (write_in_full(proc.in, buf.buf, buf.len) < 0) {\n-\t\t\t/* We do not mind if a hook does not read all refs. */\n-\t\t\tif (errno != EPIPE)\n-\t\t\t\tret = -1;\n-\t\t\tbreak;\n-\t\t}\n+\t\tstring_list_append(&opt.str_stdin, tmp.buf);\n \t}\n \n-\tstrbuf_release(&buf);\n-\n-\tx = close(proc.in);\n-\tif (!ret)\n-\t\tret = x;\n-\n-\tsigchain_pop(SIGPIPE);\n-\n-\tx = finish_command(&proc);\n-\tif (!ret)\n-\t\tret = x;\n+\tret = run_hooks(\"pre-push\", &opt);\n+\trun_hooks_opt_clear(&opt);\n+\tstrbuf_release(&tmp);\n \n \treturn ret;\n }\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"411484","messageId":"20201205014945.1502660-9-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201205014945.1502660-1-emilyshaffer@google.com","subject":"[PATCH 08/17] git-p4: use 'git hook' to run hooks","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-05T01:49:36Z","receivedAt":"2020-12-05T01:51:45Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"Instead of duplicating the behavior of run-command.h:run_hook_le() in\nPython, we can directly call 'git hook run'. As a bonus, this means\ngit-p4 learns how to find hook specifications from the Git config as\nwell as from the hookdir.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n\nNotes:\n    Maybe there is a better way to do this - I had a hard time getting this to run\n    locally, and Python is not my forte, so if anybody has a better approach I'd\n    love to just take that patch instead :)\n\n git-p4.py | 70 +++++++------------------------------------------------\n 1 file changed, 9 insertions(+), 61 deletions(-)\n\ndiff --git a/git-p4.py b/git-p4.py\nindex 6ae5bbfe99..73161a56d9 100755\n--- a/git-p4.py\n+++ b/git-p4.py\n@@ -208,70 +208,18 @@ def decode_path(path):\n \n def run_git_hook(cmd, param=[]):\n     \"\"\"Execute a hook if the hook exists.\"\"\"\n-    if verbose:\n-        sys.stderr.write(\"Looking for hook: %s\\n\" % cmd)\n-        sys.stderr.flush()\n-\n-    hooks_path = gitConfig(\"core.hooksPath\")\n-    if len(hooks_path) <= 0:\n-        hooks_path = os.path.join(os.environ[\"GIT_DIR\"], \"hooks\")\n-\n-    if not isinstance(param, list):\n-        param=[param]\n-\n-    # resolve hook file name, OS depdenent\n-    hook_file = os.path.join(hooks_path, cmd)\n-    if platform.system() == 'Windows':\n-        if not os.path.isfile(hook_file):\n-            # look for the file with an extension\n-            files = glob.glob(hook_file + \".*\")\n-            if not files:\n-                return True\n-            files.sort()\n-            hook_file = files.pop()\n-            while hook_file.upper().endswith(\".SAMPLE\"):\n-                # The file is a sample hook. We don't want it\n-                if len(files) > 0:\n-                    hook_file = files.pop()\n-                else:\n-                    return True\n-\n-    if not os.path.isfile(hook_file) or not os.access(hook_file, os.X_OK):\n+    print('ESS: entering run_git_hook')\n+    if not cmd:\n         return True\n \n-    return run_hook_command(hook_file, param) == 0\n-\n-def run_hook_command(cmd, param):\n-    \"\"\"Executes a git hook command\n-       cmd = the command line file to be executed. This can be\n-       a file that is run by OS association.\n-\n-       param = a list of parameters to pass to the cmd command\n-\n-       On windows, the extension is checked to see if it should\n-       be run with the Git for Windows Bash shell.  If there\n-       is no file extension, the file is deemed a bash shell\n-       and will be handed off to sh.exe. Otherwise, Windows\n-       will be called with the shell to handle the file assocation.\n-\n-       For non Windows operating systems, the file is called\n-       as an executable.\n-    \"\"\"\n-    cli = [cmd] + param\n-    use_shell = False\n-    if platform.system() == 'Windows':\n-        (root,ext) = os.path.splitext(cmd)\n-        if ext == \"\":\n-            exe_path = os.environ.get(\"EXEPATH\")\n-            if exe_path is None:\n-                exe_path = \"\"\n-            else:\n-                exe_path = os.path.join(exe_path, \"bin\")\n-            cli = [os.path.join(exe_path, \"SH.EXE\")] + cli\n-        else:\n-            use_shell = True\n-    return subprocess.call(cli, shell=use_shell)\n+    \"\"\"args are specified with -a <arg> -a <arg> -a <arg>\"\"\"\n+    args = (['git', 'hook', 'run'] +\n+\t    [\"-a\" + arg for arg in param] +\n+\t    [cmd])\n+    print ('ESS: args:')\n+    print (args)\n \n+    return subprocess.call(args) == 0\n \n def write_pipe(c, stdin):\n     if verbose:\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"411485","messageId":"20201205014945.1502660-11-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201205014945.1502660-1-emilyshaffer@google.com","subject":"[PATCH 10/17] hook: convert 'post-rewrite' hook to config","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-05T01:49:38Z","receivedAt":"2020-12-05T01:51:45Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"By using 'hook.h' for 'post-rewrite', we simplify hook invocations by\nnot needing to put together our own 'struct child_process' and we also\nlearn to run hooks specified in the config as well as the hook dir.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt |  2 +\n builtin/am.c               | 18 +++------\n sequencer.c                | 76 +++++++++++++++-----------------------\n 3 files changed, 36 insertions(+), 60 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 14035ef725..db290984f6 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -585,6 +585,8 @@ The hook always runs after the automatic note copying (see\n \"notes.rewrite.<command>\" in linkgit:git-config[1]) has happened, and\n thus has access to these notes.\n \n+Hooks run during 'post-rewrite' will be run in parallel by default.\n+\n The following command-specific comments apply:\n \n rebase::\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 22d147bc19..c7cad2cb32 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -449,23 +449,15 @@ static int run_applypatch_msg_hook(struct am_state *state)\n  */\n static int run_post_rewrite_hook(const struct am_state *state)\n {\n-\tstruct child_process cp = CHILD_PROCESS_INIT;\n-\tconst char *hook = find_hook(\"post-rewrite\");\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;\n \tint ret;\n \n-\tif (!hook)\n-\t\treturn 0;\n-\n-\tstrvec_push(&cp.args, hook);\n-\tstrvec_push(&cp.args, \"rebase\");\n+\tstrvec_push(&opt.args, \"rebase\");\n+\topt.path_to_stdin = am_path(state, \"rewritten\");\n \n-\tcp.in = xopen(am_path(state, \"rewritten\"), O_RDONLY);\n-\tcp.stdout_to_stderr = 1;\n-\tcp.trace2_hook_name = \"post-rewrite\";\n+\tret = run_hooks(\"post-rewrite\", &opt);\n \n-\tret = run_command(&cp);\n-\n-\tclose(cp.in);\n+\trun_hooks_opt_clear(&opt);\n \treturn ret;\n }\n \ndiff --git a/sequencer.c b/sequencer.c\nindex 5a98fd2fbc..4befd862ff 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -35,6 +35,7 @@\n #include \"rebase-interactive.h\"\n #include \"reset.h\"\n #include \"hook.h\"\n+#include \"string-list.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -1143,33 +1144,23 @@ int update_head_with_reflog(const struct commit *old_head,\n static int run_rewrite_hook(const struct object_id *oldoid,\n \t\t\t    const struct object_id *newoid)\n {\n-\tstruct child_process proc = CHILD_PROCESS_INIT;\n-\tconst char *argv[3];\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;\n+\tstruct strbuf tmp = STRBUF_INIT;\n \tint code;\n-\tstruct strbuf sb = STRBUF_INIT;\n \n-\targv[0] = find_hook(\"post-rewrite\");\n-\tif (!argv[0])\n-\t\treturn 0;\n+\tstrvec_push(&opt.args, \"amend\");\n \n-\targv[1] = \"amend\";\n-\targv[2] = NULL;\n-\n-\tproc.argv = argv;\n-\tproc.in = -1;\n-\tproc.stdout_to_stderr = 1;\n-\tproc.trace2_hook_name = \"post-rewrite\";\n-\n-\tcode = start_command(&proc);\n-\tif (code)\n-\t\treturn code;\n-\tstrbuf_addf(&sb, \"%s %s\\n\", oid_to_hex(oldoid), oid_to_hex(newoid));\n-\tsigchain_push(SIGPIPE, SIG_IGN);\n-\twrite_in_full(proc.in, sb.buf, sb.len);\n-\tclose(proc.in);\n-\tstrbuf_release(&sb);\n-\tsigchain_pop(SIGPIPE);\n-\treturn finish_command(&proc);\n+\tstrbuf_addf(&tmp,\n+\t\t    \"%s %s\",\n+\t\t    oid_to_hex(oldoid),\n+\t\t    oid_to_hex(newoid));\n+\tstring_list_append(&opt.str_stdin, tmp.buf);\n+\n+\tcode = run_hooks(\"post-rewrite\", &opt);\n+\n+\trun_hooks_opt_clear(&opt);\n+\tstrbuf_release(&tmp);\n+\treturn code;\n }\n \n void commit_post_rewrite(struct repository *r,\n@@ -4317,30 +4308,21 @@ static int pick_commits(struct repository *r,\n \t\tflush_rewritten_pending();\n \t\tif (!stat(rebase_path_rewritten_list(), &st) &&\n \t\t\t\tst.st_size > 0) {\n-\t\t\tstruct child_process child = CHILD_PROCESS_INIT;\n-\t\t\tconst char *post_rewrite_hook =\n-\t\t\t\tfind_hook(\"post-rewrite\");\n-\n-\t\t\tchild.in = open(rebase_path_rewritten_list(), O_RDONLY);\n-\t\t\tchild.git_cmd = 1;\n-\t\t\tstrvec_push(&child.args, \"notes\");\n-\t\t\tstrvec_push(&child.args, \"copy\");\n-\t\t\tstrvec_push(&child.args, \"--for-rewrite=rebase\");\n+\t\t\tstruct child_process notes_cp = CHILD_PROCESS_INIT;\n+\t\t\tstruct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_ASYNC;\n+\n+\t\t\tnotes_cp.in = open(rebase_path_rewritten_list(), O_RDONLY);\n+\t\t\tnotes_cp.git_cmd = 1;\n+\t\t\tstrvec_push(&notes_cp.args, \"notes\");\n+\t\t\tstrvec_push(&notes_cp.args, \"copy\");\n+\t\t\tstrvec_push(&notes_cp.args, \"--for-rewrite=rebase\");\n \t\t\t/* we don't care if this copying failed */\n-\t\t\trun_command(&child);\n-\n-\t\t\tif (post_rewrite_hook) {\n-\t\t\t\tstruct child_process hook = CHILD_PROCESS_INIT;\n-\n-\t\t\t\thook.in = open(rebase_path_rewritten_list(),\n-\t\t\t\t\tO_RDONLY);\n-\t\t\t\thook.stdout_to_stderr = 1;\n-\t\t\t\thook.trace2_hook_name = \"post-rewrite\";\n-\t\t\t\tstrvec_push(&hook.args, post_rewrite_hook);\n-\t\t\t\tstrvec_push(&hook.args, \"rebase\");\n-\t\t\t\t/* we don't care if this hook failed */\n-\t\t\t\trun_command(&hook);\n-\t\t\t}\n+\t\t\trun_command(&notes_cp);\n+\n+\t\t\thook_opt.path_to_stdin = rebase_path_rewritten_list();\n+\t\t\tstrvec_push(&hook_opt.args, \"rebase\");\n+\t\t\trun_hooks(\"post-rewrite\", &hook_opt);\n+\t\t\trun_hooks_opt_clear(&hook_opt);\n \t\t}\n \t\tapply_autostash(rebase_path_autostash());\n \n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"411486","messageId":"20201205014945.1502660-14-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201205014945.1502660-1-emilyshaffer@google.com","subject":"[PATCH 13/17] receive-pack: convert 'update' hook to hook.h","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-05T01:49:41Z","receivedAt":"2020-12-05T01:51:45Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"By using hook.h to invoke the 'update' hook, now hooks can be specified\nin the config in addition to the hookdir.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt       |  2 +\n builtin/receive-pack.c           | 65 +++++++++++++++++++++-----------\n t/t1416-ref-transaction-hooks.sh |  4 +-\n 3 files changed, 48 insertions(+), 23 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 3a35500132..2b3a74f249 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -359,6 +359,8 @@ 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+Hooks executed during 'update' are run in parallel by default.\n+\n [[proc-receive]]\n proc-receive\n ~~~~~~~~~~~~\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex d64a3076be..cd79ad6bdc 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -930,33 +930,56 @@ static int run_receive_hook(struct command *commands,\n \treturn status;\n }\n \n-static int run_update_hook(struct command *cmd)\n+static void hook_output_to_sideband(struct strbuf *output, void *cb_data)\n {\n-\tconst char *argv[5];\n-\tstruct child_process proc = CHILD_PROCESS_INIT;\n-\tint code;\n+\tint keepalive_active = 0;\n \n-\targv[0] = find_hook(\"update\");\n-\tif (!argv[0])\n-\t\treturn 0;\n+\tif (keepalive_in_sec <= 0)\n+\t\tuse_keepalive = KEEPALIVE_NEVER;\n+\tif (use_keepalive == KEEPALIVE_ALWAYS)\n+\t\tkeepalive_active = 1;\n \n-\targv[1] = cmd->ref_name;\n-\targv[2] = oid_to_hex(&cmd->old_oid);\n-\targv[3] = oid_to_hex(&cmd->new_oid);\n-\targv[4] = NULL;\n+\t/* send a keepalive if there is no data to write */\n+\tif (keepalive_active && !output->len) {\n+\t\tstatic const char buf[] = \"0005\\1\";\n+\t\twrite_or_die(1, buf, sizeof(buf) - 1);\n+\t\treturn;\n+\t}\n \n-\tproc.no_stdin = 1;\n-\tproc.stdout_to_stderr = 1;\n-\tproc.err = use_sideband ? -1 : 0;\n-\tproc.argv = argv;\n-\tproc.trace2_hook_name = \"update\";\n+\tif (use_keepalive == KEEPALIVE_AFTER_NUL && !keepalive_active) {\n+\t\tconst char *first_null = memchr(output->buf, '\\0', output->len);\n+\t\tif (first_null) {\n+\t\t\t/* The null bit is excluded. */\n+\t\t\tsize_t before_null = first_null - output->buf;\n+\t\t\tsize_t after_null = output->len - (before_null + 1);\n+\t\t\tkeepalive_active = 1;\n+\t\t\tsend_sideband(1, 2, output->buf, before_null, use_sideband);\n+\t\t\tsend_sideband(1, 2, first_null + 1, after_null, use_sideband);\n+\n+\t\t\treturn;\n+\t\t}\n+\t}\n+\n+\tsend_sideband(1, 2, output->buf, output->len, use_sideband);\n+}\n+\n+static int run_update_hook(struct command *cmd)\n+{\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;\n+\tint code;\n+\n+\tstrvec_pushl(&opt.args,\n+\t\t     cmd->ref_name,\n+\t\t     oid_to_hex(&cmd->old_oid),\n+\t\t     oid_to_hex(&cmd->new_oid),\n+\t\t     NULL);\n \n-\tcode = start_command(&proc);\n-\tif (code)\n-\t\treturn code;\n \tif (use_sideband)\n-\t\tcopy_to_sideband(proc.err, -1, NULL);\n-\treturn finish_command(&proc);\n+\t\topt.consume_sideband = hook_output_to_sideband;\n+\n+\tcode = run_hooks(\"update\", &opt);\n+\trun_hooks_opt_clear(&opt);\n+\treturn code;\n }\n \n static struct command *find_command_by_refname(struct command *list,\ndiff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh\nindex 17f11f5cb0..28359f6099 100755\n--- a/t/t1416-ref-transaction-hooks.sh\n+++ b/t/t1416-ref-transaction-hooks.sh\n@@ -121,10 +121,10 @@ test_expect_success 'interleaving hook calls succeed' '\n \tEOF\n \n \tcat >expect <<-EOF &&\n-\t\thooks/update refs/tags/PRE $ZERO_OID $PRE_OID\n+\t\t$(pwd)/target-repo.git/hooks/update refs/tags/PRE $ZERO_OID $PRE_OID\n \t\t$(pwd)/target-repo.git/hooks/reference-transaction prepared\n \t\t$(pwd)/target-repo.git/hooks/reference-transaction committed\n-\t\thooks/update refs/tags/POST $ZERO_OID $POST_OID\n+\t\t$(pwd)/target-repo.git/hooks/update refs/tags/POST $ZERO_OID $POST_OID\n \t\t$(pwd)/target-repo.git/hooks/reference-transaction prepared\n \t\t$(pwd)/target-repo.git/hooks/reference-transaction committed\n \tEOF\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"411482","messageId":"20201205014945.1502660-17-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201205014945.1502660-1-emilyshaffer@google.com","subject":"[PATCH 16/17] receive-pack: convert receive hooks to hook.h","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-05T01:49:44Z","receivedAt":"2020-12-05T01:51:46Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"By using the hook.h library to run receive hooks, they can be specified\nin the config as well as in the hookdir.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt |   4 +\n builtin/receive-pack.c     | 197 +++++++++++++++++--------------------\n 2 files changed, 94 insertions(+), 107 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 1412cd5846..c450f7a27e 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -314,6 +314,8 @@ will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.\n See the section on \"Quarantine Environment\" in\n linkgit:git-receive-pack[1] for some caveats.\n \n+Hooks executed during 'pre-receive' will not be parallelized.\n+\n [[update]]\n update\n ~~~~~~\n@@ -466,6 +468,8 @@ environment variables will not be set. If the client selects\n to use push options, but doesn't transmit any, the count variable\n will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.\n \n+Hooks executed during 'post-receive' are run in parallel by default.\n+\n [[post-update]]\n post-update\n ~~~~~~~~~~~\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 66af00fd8c..c30c8e3a55 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -740,7 +740,7 @@ static int check_cert_push_options(const struct string_list *push_options)\n \treturn retval;\n }\n \n-static void prepare_push_cert_sha1(struct child_process *proc)\n+static void prepare_push_cert_sha1(struct run_hooks_opt *opt)\n {\n \tstatic int already_done;\n \n@@ -764,110 +764,42 @@ static void prepare_push_cert_sha1(struct child_process *proc)\n \t\tnonce_status = check_nonce(push_cert.buf, bogs);\n \t}\n \tif (!is_null_oid(&push_cert_oid)) {\n-\t\tstrvec_pushf(&proc->env_array, \"GIT_PUSH_CERT=%s\",\n+\t\tstrvec_pushf(&opt->env, \"GIT_PUSH_CERT=%s\",\n \t\t\t     oid_to_hex(&push_cert_oid));\n-\t\tstrvec_pushf(&proc->env_array, \"GIT_PUSH_CERT_SIGNER=%s\",\n+\t\tstrvec_pushf(&opt->env, \"GIT_PUSH_CERT_SIGNER=%s\",\n \t\t\t     sigcheck.signer ? sigcheck.signer : \"\");\n-\t\tstrvec_pushf(&proc->env_array, \"GIT_PUSH_CERT_KEY=%s\",\n+\t\tstrvec_pushf(&opt->env, \"GIT_PUSH_CERT_KEY=%s\",\n \t\t\t     sigcheck.key ? sigcheck.key : \"\");\n-\t\tstrvec_pushf(&proc->env_array, \"GIT_PUSH_CERT_STATUS=%c\",\n+\t\tstrvec_pushf(&opt->env, \"GIT_PUSH_CERT_STATUS=%c\",\n \t\t\t     sigcheck.result);\n \t\tif (push_cert_nonce) {\n-\t\t\tstrvec_pushf(&proc->env_array,\n+\t\t\tstrvec_pushf(&opt->env,\n \t\t\t\t     \"GIT_PUSH_CERT_NONCE=%s\",\n \t\t\t\t     push_cert_nonce);\n-\t\t\tstrvec_pushf(&proc->env_array,\n+\t\t\tstrvec_pushf(&opt->env,\n \t\t\t\t     \"GIT_PUSH_CERT_NONCE_STATUS=%s\",\n \t\t\t\t     nonce_status);\n \t\t\tif (nonce_status == NONCE_SLOP)\n-\t\t\t\tstrvec_pushf(&proc->env_array,\n+\t\t\t\tstrvec_pushf(&opt->env,\n \t\t\t\t\t     \"GIT_PUSH_CERT_NONCE_SLOP=%ld\",\n \t\t\t\t\t     nonce_stamp_slop);\n \t\t}\n \t}\n }\n \n+struct receive_hook_feed_context {\n+\tstruct command *cmd;\n+\tint skip_broken;\n+};\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 };\n \n-typedef int (*feed_fn)(void *, const char **, size_t *);\n-static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n-\t\t\t     struct receive_hook_feed_state *feed_state)\n-{\n-\tstruct child_process proc = CHILD_PROCESS_INIT;\n-\tstruct async muxer;\n-\tconst char *argv[2];\n-\tint code;\n-\n-\targv[0] = find_hook(hook_name);\n-\tif (!argv[0])\n-\t\treturn 0;\n-\n-\targv[1] = NULL;\n-\n-\tproc.argv = argv;\n-\tproc.in = -1;\n-\tproc.stdout_to_stderr = 1;\n-\tproc.trace2_hook_name = hook_name;\n-\n-\tif (feed_state->push_options) {\n-\t\tint i;\n-\t\tfor (i = 0; i < feed_state->push_options->nr; i++)\n-\t\t\tstrvec_pushf(&proc.env_array,\n-\t\t\t\t     \"GIT_PUSH_OPTION_%d=%s\", i,\n-\t\t\t\t     feed_state->push_options->items[i].string);\n-\t\tstrvec_pushf(&proc.env_array, \"GIT_PUSH_OPTION_COUNT=%d\",\n-\t\t\t     feed_state->push_options->nr);\n-\t} else\n-\t\tstrvec_pushf(&proc.env_array, \"GIT_PUSH_OPTION_COUNT\");\n-\n-\tif (tmp_objdir)\n-\t\tstrvec_pushv(&proc.env_array, tmp_objdir_env(tmp_objdir));\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}\n-\n-\tprepare_push_cert_sha1(&proc);\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-\twhile (1) {\n-\t\tconst char *buf;\n-\t\tsize_t n;\n-\t\tif (feed(feed_state, &buf, &n))\n-\t\t\tbreak;\n-\t\tif (write_in_full(proc.in, buf, n) < 0)\n-\t\t\tbreak;\n-\t}\n-\tclose(proc.in);\n-\tif (use_sideband)\n-\t\tfinish_async(&muxer);\n-\n-\tsigchain_pop(SIGPIPE);\n-\n-\treturn finish_command(&proc);\n-}\n-\n-static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n+static int feed_receive_hook(void *state_)\n {\n \tstruct receive_hook_feed_state *state = state_;\n \tstruct command *cmd = state->cmd;\n@@ -876,9 +808,7 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\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+\t\treturn 1; /* EOF - close the pipe*/\n \tstrbuf_reset(&state->buf);\n \tif (!state->report)\n \t\tstate->report = cmd->report;\n@@ -902,32 +832,36 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\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-\t}\n \treturn 0;\n }\n \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    const struct string_list *push_options)\n+static int feed_receive_hook_cb(struct strbuf *pipe, void *pp_cb, void *pp_task_cb)\n {\n-\tstruct receive_hook_feed_state state;\n-\tint status;\n-\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-\tstate.push_options = push_options;\n-\tstatus = run_and_feed_hook(hook_name, feed_receive_hook, &state);\n-\tstrbuf_release(&state.buf);\n-\treturn status;\n+\tstruct hook *hook = pp_task_cb;\n+\tstruct receive_hook_feed_state *feed_state = hook->feed_pipe_cb_data;\n+\tint rc;\n+\n+\t/* first-time setup */\n+\tif (!feed_state) {\n+\t\tstruct hook_cb_data *hook_cb = pp_cb;\n+\t\tstruct run_hooks_opt *opt = hook_cb->options;\n+\t\tstruct receive_hook_feed_context *ctx = opt->feed_pipe_ctx;\n+\t\tif (!ctx)\n+\t\t\tBUG(\"run_hooks_opt.feed_pipe_ctx required for receive hook\");\n+\n+\t\tfeed_state = xmalloc(sizeof(struct receive_hook_feed_state));\n+\t\tstrbuf_init(&feed_state->buf, 0);\n+\t\tfeed_state->cmd = ctx->cmd;\n+\t\tfeed_state->skip_broken = ctx->skip_broken;\n+\t\tfeed_state->report = NULL;\n+\n+\t\thook->feed_pipe_cb_data = feed_state;\n+\t}\n+\n+\trc = feed_receive_hook(feed_state);\n+\tif (!rc)\n+\t\tstrbuf_addbuf(pipe, &feed_state->buf);\n+\treturn rc;\n }\n \n static void hook_output_to_sideband(struct strbuf *output, void *cb_data)\n@@ -963,6 +897,55 @@ static void hook_output_to_sideband(struct strbuf *output, void *cb_data)\n \tsend_sideband(1, 2, output->buf, output->len, use_sideband);\n }\n \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    const struct string_list *push_options)\n+{\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;\n+\tstruct receive_hook_feed_context ctx;\n+\tint rc;\n+\tstruct command *iter = commands;\n+\n+\t/* if there are no valid commands, don't invoke the hook at all. */\n+\twhile (iter && skip_broken && (iter->error_string || iter->did_not_exist))\n+\t\titer = iter->next;\n+\tif (!iter)\n+\t\treturn 0;\n+\n+\t/* pre-receive hooks should run in series as the hook updates refs */\n+\tif (!strcmp(hook_name, \"pre-receive\"))\n+\t\topt.jobs = 1;\n+\n+\tif (push_options) {\n+\t\tint i;\n+\t\tfor (i = 0; i < push_options->nr; i++)\n+\t\t\tstrvec_pushf(&opt.env, \"GIT_PUSH_OPTION_%d=%s\", i,\n+\t\t\t\t     push_options->items[i].string);\n+\t\tstrvec_pushf(&opt.env, \"GIT_PUSH_OPTION_COUNT=%d\", push_options->nr);\n+\t} else\n+\t\tstrvec_push(&opt.env, \"GIT_PUSH_OPTION_COUNT\");\n+\n+\tif (tmp_objdir)\n+\t\tstrvec_pushv(&opt.env, tmp_objdir_env(tmp_objdir));\n+\n+\tprepare_push_cert_sha1(&opt);\n+\n+\t/* set up sideband printer */\n+\tif (use_sideband)\n+\t\topt.consume_sideband = hook_output_to_sideband;\n+\n+\t/* set up stdin callback */\n+\tctx.cmd = commands;\n+\tctx.skip_broken = skip_broken;\n+\topt.feed_pipe = feed_receive_hook_cb;\n+\topt.feed_pipe_ctx = &ctx;\n+\n+\trc = run_hooks(hook_name, &opt);\n+\trun_hooks_opt_clear(&opt);\n+\treturn rc;\n+}\n+\n static int run_update_hook(struct command *cmd)\n {\n \tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"411483","messageId":"20201205014945.1502660-18-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201205014945.1502660-1-emilyshaffer@google.com","subject":"[PATCH 17/17] run-command: stop thinking about hooks","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-05T01:49:45Z","receivedAt":"2020-12-05T01:51:46Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"hook.h has replaced all run-command.h hook-related functionality.\nrun-command.h:run_hooks_le/ve and find_hook are no longer used anywhere\nin the codebase. So, let's delete the dead code - or, in the one case\nwhere it's still needed, move it to an internal function in hook.c.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n hook.c        | 39 ++++++++++++++++++++++++++++--\n run-command.c | 66 ---------------------------------------------------\n run-command.h | 24 -------------------\n 3 files changed, 37 insertions(+), 92 deletions(-)\n\ndiff --git a/hook.c b/hook.c\nindex 78d7721b74..85fd59865d 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -197,6 +197,41 @@ static int should_include_hookdir(const char *path, enum hookdir_opt cfg)\n \t}\n }\n \n+static const char *find_legacy_hook(const char *name)\n+{\n+\tstatic struct strbuf path = STRBUF_INIT;\n+\n+\tstrbuf_reset(&path);\n+\tstrbuf_git_path(&path, \"hooks/%s\", name);\n+\tif (access(path.buf, X_OK) < 0) {\n+\t\tint err = errno;\n+\n+#ifdef STRIP_EXTENSION\n+\t\tstrbuf_addstr(&path, STRIP_EXTENSION);\n+\t\tif (access(path.buf, X_OK) >= 0)\n+\t\t\treturn path.buf;\n+\t\tif (errno == EACCES)\n+\t\t\terr = errno;\n+#endif\n+\n+\t\tif (err == EACCES && advice_ignored_hook) {\n+\t\t\tstatic struct string_list advise_given = STRING_LIST_INIT_DUP;\n+\n+\t\t\tif (!string_list_lookup(&advise_given, name)) {\n+\t\t\t\tstring_list_insert(&advise_given, name);\n+\t\t\t\tadvise(_(\"The '%s' hook was ignored because \"\n+\t\t\t\t\t \"it's not set as executable.\\n\"\n+\t\t\t\t\t \"You can disable this warning with \"\n+\t\t\t\t\t \"`git config advice.ignoredHook false`.\"),\n+\t\t\t\t       path.buf);\n+\t\t\t}\n+\t\t}\n+\t\treturn NULL;\n+\t}\n+\treturn path.buf;\n+}\n+\n+\n struct list_head* hook_list(const struct strbuf* hookname)\n {\n \tstruct strbuf hook_key = STRBUF_INIT;\n@@ -214,7 +249,7 @@ struct list_head* hook_list(const struct strbuf* hookname)\n \tgit_config(hook_config_lookup, (void*)&cb_data);\n \n \tif (have_git_dir())\n-\t\tlegacy_hook_path = find_hook(hookname->buf);\n+\t\tlegacy_hook_path = find_legacy_hook(hookname->buf);\n \n \t/* Unconditionally add legacy hook, but annotate it. */\n \tif (legacy_hook_path) {\n@@ -245,7 +280,7 @@ int hook_exists(const char *hookname, enum hookdir_opt should_run_hookdir)\n \tint could_run_hookdir = (should_run_hookdir == hookdir_interactive ||\n \t\t\t\tshould_run_hookdir == hookdir_warn ||\n \t\t\t\tshould_run_hookdir == hookdir_yes)\n-\t\t\t\t&& !!find_hook(hookname);\n+\t\t\t\t&& !!find_legacy_hook(hookname);\n \n \tstrbuf_addf(&hook_key, \"hook.%s.command\", hookname);\n \ndiff --git a/run-command.c b/run-command.c\nindex 0dce6bec83..16656135dd 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -1310,72 +1310,6 @@ int async_with_fork(void)\n #endif\n }\n \n-const char *find_hook(const char *name)\n-{\n-\tstatic struct strbuf path = STRBUF_INIT;\n-\n-\tstrbuf_reset(&path);\n-\tstrbuf_git_path(&path, \"hooks/%s\", name);\n-\tif (access(path.buf, X_OK) < 0) {\n-\t\tint err = errno;\n-\n-#ifdef STRIP_EXTENSION\n-\t\tstrbuf_addstr(&path, STRIP_EXTENSION);\n-\t\tif (access(path.buf, X_OK) >= 0)\n-\t\t\treturn path.buf;\n-\t\tif (errno == EACCES)\n-\t\t\terr = errno;\n-#endif\n-\n-\t\tif (err == EACCES && advice_ignored_hook) {\n-\t\t\tstatic struct string_list advise_given = STRING_LIST_INIT_DUP;\n-\n-\t\t\tif (!string_list_lookup(&advise_given, name)) {\n-\t\t\t\tstring_list_insert(&advise_given, name);\n-\t\t\t\tadvise(_(\"The '%s' hook was ignored because \"\n-\t\t\t\t\t \"it's not set as executable.\\n\"\n-\t\t\t\t\t \"You can disable this warning with \"\n-\t\t\t\t\t \"`git config advice.ignoredHook false`.\"),\n-\t\t\t\t       path.buf);\n-\t\t\t}\n-\t\t}\n-\t\treturn NULL;\n-\t}\n-\treturn path.buf;\n-}\n-\n-int run_hook_ve(const char *const *env, const char *name, va_list args)\n-{\n-\tstruct child_process hook = CHILD_PROCESS_INIT;\n-\tconst char *p;\n-\n-\tp = find_hook(name);\n-\tif (!p)\n-\t\treturn 0;\n-\n-\tstrvec_push(&hook.args, p);\n-\twhile ((p = va_arg(args, const char *)))\n-\t\tstrvec_push(&hook.args, p);\n-\thook.env = env;\n-\thook.no_stdin = 1;\n-\thook.stdout_to_stderr = 1;\n-\thook.trace2_hook_name = name;\n-\n-\treturn run_command(&hook);\n-}\n-\n-int run_hook_le(const char *const *env, const char *name, ...)\n-{\n-\tva_list args;\n-\tint ret;\n-\n-\tva_start(args, name);\n-\tret = run_hook_ve(env, name, args);\n-\tva_end(args);\n-\n-\treturn ret;\n-}\n-\n struct io_pump {\n \t/* initialized by caller */\n \tint fd;\ndiff --git a/run-command.h b/run-command.h\nindex 2ad8271f56..e67bd22c5a 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -194,30 +194,6 @@ int finish_command_in_signal(struct child_process *);\n  */\n int run_command(struct child_process *);\n \n-/*\n- * Returns the path to the hook file, or NULL if the hook is missing\n- * or disabled. Note that this points to static storage that will be\n- * overwritten by further calls to find_hook and run_hook_*.\n- */\n-const char *find_hook(const char *name);\n-\n-/**\n- * Run a hook.\n- * The first argument is a pathname to an index file, or NULL\n- * if the hook uses the default index file or no index is needed.\n- * The second argument is the name of the hook.\n- * The further arguments correspond to the hook arguments.\n- * The last argument has to be NULL to terminate the arguments list.\n- * If the hook does not exist or is not executable, the return\n- * value will be zero.\n- * If it is executable, the hook will be executed and the exit\n- * status of the hook is returned.\n- * On execution, .stdout_to_stderr and .no_stdin will be set.\n- */\n-LAST_ARG_MUST_BE_NULL\n-int run_hook_le(const char *const *env, const char *name, ...);\n-int run_hook_ve(const char *const *env, const char *name, va_list args);\n-\n /*\n  * Trigger an auto-gc\n  */\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"411795","messageId":"20201208230213.GM36751@google.com","threadId":"54422","inReplyTo":"20201205014945.1502660-11-emilyshaffer@google.com","subject":"Re: [PATCH 10/17] hook: convert 'post-rewrite' hook to config","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2020-12-08T23:02:13Z","receivedAt":"2020-12-08T23:03:21Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2020.12.04 17:49, Emily Shaffer wrote:\n> diff --git a/sequencer.c b/sequencer.c\n> index 5a98fd2fbc..4befd862ff 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -35,6 +35,7 @@\n>  #include \"rebase-interactive.h\"\n>  #include \"reset.h\"\n>  #include \"hook.h\"\n> +#include \"string-list.h\"\n>  \n>  #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n>  \n> @@ -1143,33 +1144,23 @@ int update_head_with_reflog(const struct commit *old_head,\n>  static int run_rewrite_hook(const struct object_id *oldoid,\n>  \t\t\t    const struct object_id *newoid)\n>  {\n> -\tstruct child_process proc = CHILD_PROCESS_INIT;\n> -\tconst char *argv[3];\n> +\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;\n> +\tstruct strbuf tmp = STRBUF_INIT;\n>  \tint code;\n> -\tstruct strbuf sb = STRBUF_INIT;\n>  \n> -\targv[0] = find_hook(\"post-rewrite\");\n> -\tif (!argv[0])\n> -\t\treturn 0;\n> +\tstrvec_push(&opt.args, \"amend\");\n>  \n> -\targv[1] = \"amend\";\n> -\targv[2] = NULL;\n> -\n> -\tproc.argv = argv;\n> -\tproc.in = -1;\n> -\tproc.stdout_to_stderr = 1;\n> -\tproc.trace2_hook_name = \"post-rewrite\";\n> -\n> -\tcode = start_command(&proc);\n> -\tif (code)\n> -\t\treturn code;\n> -\tstrbuf_addf(&sb, \"%s %s\\n\", oid_to_hex(oldoid), oid_to_hex(newoid));\n> -\tsigchain_push(SIGPIPE, SIG_IGN);\n\nHere and in a few other later patches, we're removing some signal\nhandling that doesn't seem to be replicated in the run_hooks()\nimplementation. Can you add a note to the commit message about why this\nis OK?\n\n> -\twrite_in_full(proc.in, sb.buf, sb.len);\n> -\tclose(proc.in);\n> -\tstrbuf_release(&sb);\n> -\tsigchain_pop(SIGPIPE);\n> -\treturn finish_command(&proc);\n> +\tstrbuf_addf(&tmp,\n> +\t\t    \"%s %s\",\n> +\t\t    oid_to_hex(oldoid),\n> +\t\t    oid_to_hex(newoid));\n> +\tstring_list_append(&opt.str_stdin, tmp.buf);\n> +\n> +\tcode = run_hooks(\"post-rewrite\", &opt);\n> +\n> +\trun_hooks_opt_clear(&opt);\n> +\tstrbuf_release(&tmp);\n> +\treturn code;\n>  }\n>  \n>  void commit_post_rewrite(struct repository *r,\n> @@ -4317,30 +4308,21 @@ static int pick_commits(struct repository *r,\n>  \t\tflush_rewritten_pending();\n>  \t\tif (!stat(rebase_path_rewritten_list(), &st) &&\n>  \t\t\t\tst.st_size > 0) {\n> -\t\t\tstruct child_process child = CHILD_PROCESS_INIT;\n> -\t\t\tconst char *post_rewrite_hook =\n> -\t\t\t\tfind_hook(\"post-rewrite\");\n> -\n> -\t\t\tchild.in = open(rebase_path_rewritten_list(), O_RDONLY);\n> -\t\t\tchild.git_cmd = 1;\n> -\t\t\tstrvec_push(&child.args, \"notes\");\n> -\t\t\tstrvec_push(&child.args, \"copy\");\n> -\t\t\tstrvec_push(&child.args, \"--for-rewrite=rebase\");\n> +\t\t\tstruct child_process notes_cp = CHILD_PROCESS_INIT;\n> +\t\t\tstruct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_ASYNC;\n> +\n> +\t\t\tnotes_cp.in = open(rebase_path_rewritten_list(), O_RDONLY);\n> +\t\t\tnotes_cp.git_cmd = 1;\n> +\t\t\tstrvec_push(&notes_cp.args, \"notes\");\n> +\t\t\tstrvec_push(&notes_cp.args, \"copy\");\n> +\t\t\tstrvec_push(&notes_cp.args, \"--for-rewrite=rebase\");\n>  \t\t\t/* we don't care if this copying failed */\n> -\t\t\trun_command(&child);\n> -\n> -\t\t\tif (post_rewrite_hook) {\n> -\t\t\t\tstruct child_process hook = CHILD_PROCESS_INIT;\n> -\n> -\t\t\t\thook.in = open(rebase_path_rewritten_list(),\n> -\t\t\t\t\tO_RDONLY);\n> -\t\t\t\thook.stdout_to_stderr = 1;\n> -\t\t\t\thook.trace2_hook_name = \"post-rewrite\";\n> -\t\t\t\tstrvec_push(&hook.args, post_rewrite_hook);\n> -\t\t\t\tstrvec_push(&hook.args, \"rebase\");\n> -\t\t\t\t/* we don't care if this hook failed */\n> -\t\t\t\trun_command(&hook);\n> -\t\t\t}\n> +\t\t\trun_command(&notes_cp);\n> +\n> +\t\t\thook_opt.path_to_stdin = rebase_path_rewritten_list();\n> +\t\t\tstrvec_push(&hook_opt.args, \"rebase\");\n> +\t\t\trun_hooks(\"post-rewrite\", &hook_opt);\n> +\t\t\trun_hooks_opt_clear(&hook_opt);\n>  \t\t}\n>  \t\tapply_autostash(rebase_path_autostash());\n>  \n> -- \n> 2.28.0.rc0.142.g3c755180ce-goog\n> \n"},{"id":"412312","messageId":"20201215234218.GE3783238@google.com","threadId":"54422","inReplyTo":"20201208230213.GM36751@google.com","subject":"Re: [PATCH 10/17] hook: convert 'post-rewrite' hook to config","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-15T23:42:18Z","receivedAt":"2020-12-16T00:03:19Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Tue, Dec 08, 2020 at 03:02:13PM -0800, Josh Steadmon wrote:\n> \n> On 2020.12.04 17:49, Emily Shaffer wrote:\n> > diff --git a/sequencer.c b/sequencer.c\n> > index 5a98fd2fbc..4befd862ff 100644\n> > --- a/sequencer.c\n> > +++ b/sequencer.c\n> > @@ -35,6 +35,7 @@\n> >  #include \"rebase-interactive.h\"\n> >  #include \"reset.h\"\n> >  #include \"hook.h\"\n> > +#include \"string-list.h\"\n> >  \n> >  #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n> >  \n> > @@ -1143,33 +1144,23 @@ int update_head_with_reflog(const struct commit *old_head,\n> >  static int run_rewrite_hook(const struct object_id *oldoid,\n> >  \t\t\t    const struct object_id *newoid)\n> >  {\n> > -\tstruct child_process proc = CHILD_PROCESS_INIT;\n> > -\tconst char *argv[3];\n> > +\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;\n> > +\tstruct strbuf tmp = STRBUF_INIT;\n> >  \tint code;\n> > -\tstruct strbuf sb = STRBUF_INIT;\n> >  \n> > -\targv[0] = find_hook(\"post-rewrite\");\n> > -\tif (!argv[0])\n> > -\t\treturn 0;\n> > +\tstrvec_push(&opt.args, \"amend\");\n> >  \n> > -\targv[1] = \"amend\";\n> > -\targv[2] = NULL;\n> > -\n> > -\tproc.argv = argv;\n> > -\tproc.in = -1;\n> > -\tproc.stdout_to_stderr = 1;\n> > -\tproc.trace2_hook_name = \"post-rewrite\";\n> > -\n> > -\tcode = start_command(&proc);\n> > -\tif (code)\n> > -\t\treturn code;\n> > -\tstrbuf_addf(&sb, \"%s %s\\n\", oid_to_hex(oldoid), oid_to_hex(newoid));\n> > -\tsigchain_push(SIGPIPE, SIG_IGN);\n> \n> Here and in a few other later patches, we're removing some signal\n> handling that doesn't seem to be replicated in the run_hooks()\n> implementation. Can you add a note to the commit message about why this\n> is OK?\n\nYeah, I have added it to the commit message for next rollup. I think\noffline you said you found it - but this signal handling was added when\nI taught run_processes_parallel() to use a callback to provide stdin. It\nlives in run-command.h now.\n\n - Emily\n"},{"id":"412315","messageId":"20201216002712.GP36751@google.com","threadId":"54422","inReplyTo":"20201205014945.1502660-9-emilyshaffer@google.com","subject":"Re: [PATCH 08/17] git-p4: use 'git hook' to run hooks","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2020-12-16T00:27:12Z","receivedAt":"2020-12-16T00:28:02Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2020.12.04 17:49, Emily Shaffer wrote:\n> diff --git a/git-p4.py b/git-p4.py\n> index 6ae5bbfe99..73161a56d9 100755\n> --- a/git-p4.py\n> +++ b/git-p4.py\n> @@ -208,70 +208,18 @@ def decode_path(path):\n>  \n>  def run_git_hook(cmd, param=[]):\n>      \"\"\"Execute a hook if the hook exists.\"\"\"\n> -    if verbose:\n> -        sys.stderr.write(\"Looking for hook: %s\\n\" % cmd)\n> -        sys.stderr.flush()\n> -\n> -    hooks_path = gitConfig(\"core.hooksPath\")\n> -    if len(hooks_path) <= 0:\n> -        hooks_path = os.path.join(os.environ[\"GIT_DIR\"], \"hooks\")\n> -\n> -    if not isinstance(param, list):\n> -        param=[param]\n> -\n> -    # resolve hook file name, OS depdenent\n> -    hook_file = os.path.join(hooks_path, cmd)\n> -    if platform.system() == 'Windows':\n> -        if not os.path.isfile(hook_file):\n> -            # look for the file with an extension\n> -            files = glob.glob(hook_file + \".*\")\n> -            if not files:\n> -                return True\n> -            files.sort()\n> -            hook_file = files.pop()\n> -            while hook_file.upper().endswith(\".SAMPLE\"):\n> -                # The file is a sample hook. We don't want it\n> -                if len(files) > 0:\n> -                    hook_file = files.pop()\n> -                else:\n> -                    return True\n> -\n> -    if not os.path.isfile(hook_file) or not os.access(hook_file, os.X_OK):\n> +    print('ESS: entering run_git_hook')\n\nLooks like a stray debug message here?\n\n\n> +    if not cmd:\n>          return True\n>  \n> -    return run_hook_command(hook_file, param) == 0\n> -\n> -def run_hook_command(cmd, param):\n> -    \"\"\"Executes a git hook command\n> -       cmd = the command line file to be executed. This can be\n> -       a file that is run by OS association.\n> -\n> -       param = a list of parameters to pass to the cmd command\n> -\n> -       On windows, the extension is checked to see if it should\n> -       be run with the Git for Windows Bash shell.  If there\n> -       is no file extension, the file is deemed a bash shell\n> -       and will be handed off to sh.exe. Otherwise, Windows\n> -       will be called with the shell to handle the file assocation.\n> -\n> -       For non Windows operating systems, the file is called\n> -       as an executable.\n> -    \"\"\"\n> -    cli = [cmd] + param\n> -    use_shell = False\n> -    if platform.system() == 'Windows':\n> -        (root,ext) = os.path.splitext(cmd)\n> -        if ext == \"\":\n> -            exe_path = os.environ.get(\"EXEPATH\")\n> -            if exe_path is None:\n> -                exe_path = \"\"\n> -            else:\n> -                exe_path = os.path.join(exe_path, \"bin\")\n> -            cli = [os.path.join(exe_path, \"SH.EXE\")] + cli\n> -        else:\n> -            use_shell = True\n> -    return subprocess.call(cli, shell=use_shell)\n> +    \"\"\"args are specified with -a <arg> -a <arg> -a <arg>\"\"\"\n> +    args = (['git', 'hook', 'run'] +\n> +\t    [\"-a\" + arg for arg in param] +\n> +\t    [cmd])\n> +    print ('ESS: args:')\n\nHere as well.\n\n\n> +    print (args)\n>  \n> +    return subprocess.call(args) == 0\n>  \n>  def write_pipe(c, stdin):\n>      if verbose:\n> -- \n> 2.28.0.rc0.142.g3c755180ce-goog\n> \n"},{"id":"412317","messageId":"20201216003126.GQ36751@google.com","threadId":"54422","inReplyTo":"20201014232517.3068298-1-emilyshaffer@google.com","subject":"Re: [PATCH] commit: use config-based hooks (config-based hooks part II)","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2020-12-16T00:31:26Z","receivedAt":"2020-12-16T00:32:31Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2020.10.14 16:25, Emily Shaffer wrote:\n> As part of the adoption of config-based hooks, teach run_commit_hook()\n> to call hook.h instead of run-command.h. This covers 'pre-commit',\n> 'commit-msg', and 'prepare-commit-msg'. Additionally, ask the hook\n> library - not run-command - whether any hooks will be run, as it's\n> possible hooks may exist in the config but not the hookdir.\n> \n> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>\n> ---\n\nApart from the minor issues I noted on patches 8 & 10, this looks good\nto me.\n\nReviewed-by: Josh Steadmon <steadmon@google.com>\n"},{"id":"412428","messageId":"20201216201906.GG3783238@google.com","threadId":"54422","inReplyTo":"20201216002712.GP36751@google.com","subject":"Re: [PATCH 08/17] git-p4: use 'git hook' to run hooks","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-16T20:19:06Z","receivedAt":"2020-12-16T20:20:08Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Tue, Dec 15, 2020 at 04:27:12PM -0800, Josh Steadmon wrote:\n> \n> On 2020.12.04 17:49, Emily Shaffer wrote:\n> > +    print('ESS: entering run_git_hook')\n> \n> Looks like a stray debug message here?\n> \n...\n> > +    print ('ESS: args:')\n> \n> Here as well.\n\n\n:X Have fixed locally and will include with a new reroll this week.\nOops. :)\n\n - Emily\n"},{"id":"412810","messageId":"20201222000435.1529768-1-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201205014945.1502660-1-emilyshaffer@google.com","subject":"[PATCH v3 00/17] use config-based hooks (config-based hooks part II)","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-22T00:04:18Z","receivedAt":"2020-12-22T00:05:25Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"Since v2:\n - Renamed 'master' to 'main' in new t5411 (proc-receive) test.\n - Removed some accidentally included debug strings.\n - Fixed a nasty bug in the reference-transaction hook conversion where calling\n   'oid_to_hex()' would invalidate references to the output of earlier\n   'oid_to_hex()' runs farther up the callstack. Instead, the hook callsite now\n   uses 'oid_to_hex_r()'.\n\nAnother thing I wanted to do in this series but ended up not having time\nfor before the holidays was to figure out a way to consolidate\nDocumentation/githooks.txt and Documentation/git-hook.txt. My personal\npreference would be to remove githooks.txt's contents, move the \"Hooks\"\nheader from there into git-hook.txt, and have 'git help githooks'/'git\nhelp hooks' redirect to git-hook.txt; I don't have a patch to share here\nbecause I ran out of time before vacation :) What do others envision the\ndocumentation looking like?\n\nThanks!\n - Emily\n\nCI run: https://github.com/nasamuffin/git/actions/runs/436905873\n\nEmily Shaffer (17):\n  commit: use config-based hooks\n  am: convert applypatch hooks to use config\n  merge: use config-based hooks for post-merge hook\n  gc: use hook library for pre-auto-gc hook\n  rebase: teach pre-rebase to use hook.h\n  read-cache: convert post-index-change hook to use config\n  receive-pack: convert push-to-checkout hook to hook.h\n  git-p4: use 'git hook' to run hooks\n  hooks: convert 'post-checkout' hook to hook library\n  hook: convert 'post-rewrite' hook to config\n  transport: convert pre-push hook to use config\n  reference-transaction: look for hooks in config\n  receive-pack: convert 'update' hook to hook.h\n  proc-receive: acquire hook list from hook.h\n  post-update: use hook.h library\n  receive-pack: convert receive hooks to hook.h\n  run-command: stop thinking about hooks\n\n Documentation/githooks.txt                    |  45 +++\n builtin/am.c                                  |  30 +-\n builtin/checkout.c                            |  16 +-\n builtin/clone.c                               |   7 +-\n builtin/commit.c                              |  11 +-\n builtin/gc.c                                  |   4 +-\n builtin/merge.c                               |  14 +-\n builtin/rebase.c                              |   7 +-\n builtin/receive-pack.c                        | 326 ++++++++++--------\n builtin/worktree.c                            |  30 +-\n commit.c                                      |  20 +-\n commit.h                                      |   3 +-\n git-p4.py                                     |  67 +---\n hook.c                                        |  39 ++-\n read-cache.c                                  |  12 +-\n refs.c                                        |  38 +-\n reset.c                                       |  15 +-\n run-command.c                                 |  66 ----\n run-command.h                                 |  24 --\n sequencer.c                                   |  83 ++---\n t/t1416-ref-transaction-hooks.sh              |  12 +-\n t/t5411/test-0015-too-many-hooks-error.sh     |  47 +++\n ...3-pre-commit-and-pre-merge-commit-hooks.sh |  17 +-\n transport.c                                   |  55 +--\n 24 files changed, 493 insertions(+), 495 deletions(-)\n create mode 100644 t/t5411/test-0015-too-many-hooks-error.sh\n\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"412811","messageId":"20201222000435.1529768-5-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201222000435.1529768-1-emilyshaffer@google.com","subject":"[PATCH v3 04/17] gc: use hook library for pre-auto-gc hook","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-22T00:04:22Z","receivedAt":"2020-12-22T00:06:14Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"Using the hook.h library instead of the run-command.h library to run\npre-auto-gc means that those hooks can be set up in config files, as\nwell as in the hookdir. pre-auto-gc is called only from builtin/gc.c.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt | 2 ++\n builtin/gc.c               | 4 +++-\n 2 files changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex f6ddf1aa22..d74308dc20 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -553,6 +553,8 @@ This hook is invoked by `git gc --auto` (see linkgit:git-gc[1]). It\n takes no parameter, and exiting with non-zero status from this script\n causes the `git gc --auto` to abort.\n \n+Hooks run during 'pre-auto-gc' will be run in parallel by default.\n+\n post-rewrite\n ~~~~~~~~~~~~\n \ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex b57fda4924..edcc873c70 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -32,6 +32,7 @@\n #include \"remote.h\"\n #include \"object-store.h\"\n #include \"exec-cmd.h\"\n+#include \"hook.h\"\n \n #define FAILED_RUN \"failed to run %s\"\n \n@@ -340,6 +341,7 @@ static void add_repack_incremental_option(void)\n \n static int need_to_gc(void)\n {\n+\tstruct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_ASYNC;\n \t/*\n \t * Setting gc.auto to 0 or negative can disable the\n \t * automatic gc.\n@@ -386,7 +388,7 @@ static int need_to_gc(void)\n \telse\n \t\treturn 0;\n \n-\tif (run_hook_le(NULL, \"pre-auto-gc\", NULL))\n+\tif (run_hooks(\"pre-auto-gc\", &hook_opt))\n \t\treturn 0;\n \treturn 1;\n }\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"412812","messageId":"20201222000435.1529768-2-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201222000435.1529768-1-emilyshaffer@google.com","subject":"[PATCH v3 01/17] commit: use config-based hooks","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-22T00:04:19Z","receivedAt":"2020-12-22T00:06:14Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"As part of the adoption of config-based hooks, teach run_commit_hook()\nto call hook.h instead of run-command.h. This covers 'pre-commit',\n'commit-msg', and 'prepare-commit-msg'. Additionally, ask the hook\nlibrary - not run-command - whether any hooks will be run, as it's\npossible hooks may exist in the config but not the hookdir.\n\nBecause all but 'post-commit' hooks are expected to make some state\nchange, force all but 'post-commit' hook to run in series. 'post-commit'\n\"is meant primarily for notification, and cannot affect the outcome of\n`git commit`,\" so it is fine to run in parallel.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt                    | 10 ++++++++++\n builtin/commit.c                              | 11 +++++-----\n builtin/merge.c                               |  9 +++++----\n commit.c                                      | 20 +++++++++++++------\n commit.h                                      |  3 ++-\n sequencer.c                                   |  7 ++++---\n ...3-pre-commit-and-pre-merge-commit-hooks.sh | 17 ++++++++++++++--\n 7 files changed, 56 insertions(+), 21 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex ffccfc7760..8b352be43f 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -103,6 +103,8 @@ The default 'pre-commit' hook, when enabled--and with the\n `hooks.allownonascii` config option unset or set to false--prevents\n the use of non-ASCII filenames.\n \n+Hooks executed during 'pre-commit' will not be parallelized.\n+\n pre-merge-commit\n ~~~~~~~~~~~~~~~~\n \n@@ -125,6 +127,8 @@ need to be resolved and the result committed separately (see\n linkgit:git-merge[1]). At that point, this hook will not be executed,\n but the 'pre-commit' hook will, if it is enabled.\n \n+Hooks executed during 'pre-merge-commit' will not be parallelized.\n+\n prepare-commit-msg\n ~~~~~~~~~~~~~~~~~~\n \n@@ -150,6 +154,8 @@ be used as replacement for pre-commit hook.\n The sample `prepare-commit-msg` hook that comes with Git removes the\n help message found in the commented portion of the commit template.\n \n+Hooks executed during 'prepare-commit-msg' will not be parallelized.\n+\n commit-msg\n ~~~~~~~~~~\n \n@@ -166,6 +172,8 @@ file.\n The default 'commit-msg' hook, when enabled, detects duplicate\n `Signed-off-by` trailers, and aborts the commit if one is found.\n \n+Hooks executed during 'commit-msg' will not be parallelized.\n+\n post-commit\n ~~~~~~~~~~~\n \n@@ -175,6 +183,8 @@ invoked after a commit is made.\n This hook is meant primarily for notification, and cannot affect\n the outcome of `git commit`.\n \n+Hooks executed during 'post-commit' will run in parallel by default.\n+\n pre-rebase\n ~~~~~~~~~~\n \ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 505fe60956..f4dea2b510 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -36,6 +36,7 @@\n #include \"help.h\"\n #include \"commit-reach.h\"\n #include \"commit-graph.h\"\n+#include \"hook.h\"\n \n static const char * const builtin_commit_usage[] = {\n \tN_(\"git commit [<options>] [--] <pathspec>...\"),\n@@ -699,7 +700,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \t/* This checks and barfs if author is badly specified */\n \tdetermine_author_info(author_ident);\n \n-\tif (!no_verify && run_commit_hook(use_editor, index_file, \"pre-commit\", NULL))\n+\tif (!no_verify && run_commit_hook(use_editor, 0, index_file, \"pre-commit\", NULL))\n \t\treturn 0;\n \n \tif (squash_message) {\n@@ -983,7 +984,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \t\treturn 0;\n \t}\n \n-\tif (!no_verify && find_hook(\"pre-commit\")) {\n+\tif (!no_verify && hook_exists(\"pre-commit\", configured_hookdir_opt())) {\n \t\t/*\n \t\t * Re-read the index as pre-commit hook could have updated it,\n \t\t * and write it out as a tree.  We must do this before we invoke\n@@ -998,7 +999,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \t\treturn 0;\n \t}\n \n-\tif (run_commit_hook(use_editor, index_file, \"prepare-commit-msg\",\n+\tif (run_commit_hook(use_editor, 0, index_file, \"prepare-commit-msg\",\n \t\t\t    git_path_commit_editmsg(), hook_arg1, hook_arg2, NULL))\n \t\treturn 0;\n \n@@ -1015,7 +1016,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \t}\n \n \tif (!no_verify &&\n-\t    run_commit_hook(use_editor, index_file, \"commit-msg\", git_path_commit_editmsg(), NULL)) {\n+\t    run_commit_hook(use_editor, 0, index_file, \"commit-msg\", git_path_commit_editmsg(), NULL)) {\n \t\treturn 0;\n \t}\n \n@@ -1701,7 +1702,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \n \trepo_rerere(the_repository, 0);\n \trun_auto_maintenance(quiet);\n-\trun_commit_hook(use_editor, get_index_file(), \"post-commit\", NULL);\n+\trun_commit_hook(use_editor, 1, get_index_file(), \"post-commit\", NULL);\n \tif (amend && !no_post_rewrite) {\n \t\tcommit_post_rewrite(the_repository, current_head, &oid);\n \t}\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 1cff730715..d654b6923c 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -42,6 +42,7 @@\n #include \"commit-reach.h\"\n #include \"wt-status.h\"\n #include \"commit-graph.h\"\n+#include \"hook.h\"\n \n #define DEFAULT_TWOHEAD (1<<0)\n #define DEFAULT_OCTOPUS (1<<1)\n@@ -836,14 +837,14 @@ static void prepare_to_commit(struct commit_list *remoteheads)\n \tstruct strbuf msg = STRBUF_INIT;\n \tconst char *index_file = get_index_file();\n \n-\tif (!no_verify && run_commit_hook(0 < option_edit, index_file, \"pre-merge-commit\", NULL))\n+\tif (!no_verify && run_commit_hook(0 < option_edit, 0, index_file, \"pre-merge-commit\", NULL))\n \t\tabort_commit(remoteheads, NULL);\n \t/*\n \t * Re-read the index as pre-merge-commit hook could have updated it,\n \t * and write it out as a tree.  We must do this before we invoke\n \t * the editor and after we invoke run_status above.\n \t */\n-\tif (find_hook(\"pre-merge-commit\"))\n+\tif (hook_exists(\"pre-merge-commit\", configured_hookdir_opt()))\n \t\tdiscard_cache();\n \tread_cache_from(index_file);\n \tstrbuf_addbuf(&msg, &merge_msg);\n@@ -864,7 +865,7 @@ static void prepare_to_commit(struct commit_list *remoteheads)\n \t\tappend_signoff(&msg, ignore_non_trailer(msg.buf, msg.len), 0);\n \twrite_merge_heads(remoteheads);\n \twrite_file_buf(git_path_merge_msg(the_repository), msg.buf, msg.len);\n-\tif (run_commit_hook(0 < option_edit, get_index_file(), \"prepare-commit-msg\",\n+\tif (run_commit_hook(0 < option_edit, 0, get_index_file(), \"prepare-commit-msg\",\n \t\t\t    git_path_merge_msg(the_repository), \"merge\", NULL))\n \t\tabort_commit(remoteheads, NULL);\n \tif (0 < option_edit) {\n@@ -872,7 +873,7 @@ static void prepare_to_commit(struct commit_list *remoteheads)\n \t\t\tabort_commit(remoteheads, NULL);\n \t}\n \n-\tif (!no_verify && run_commit_hook(0 < option_edit, get_index_file(),\n+\tif (!no_verify && run_commit_hook(0 < option_edit, 0, get_index_file(),\n \t\t\t\t\t  \"commit-msg\",\n \t\t\t\t\t  git_path_merge_msg(the_repository), NULL))\n \t\tabort_commit(remoteheads, NULL);\ndiff --git a/commit.c b/commit.c\nindex fe1fa3dc41..1bad721a20 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -21,6 +21,7 @@\n #include \"commit-reach.h\"\n #include \"run-command.h\"\n #include \"shallow.h\"\n+#include \"hook.h\"\n \n static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);\n \n@@ -1630,25 +1631,32 @@ size_t ignore_non_trailer(const char *buf, size_t len)\n \treturn boc ? len - boc : len - cutoff;\n }\n \n-int run_commit_hook(int editor_is_used, const char *index_file,\n+int run_commit_hook(int editor_is_used, int parallelize, const char *index_file,\n \t\t    const char *name, ...)\n {\n-\tstruct strvec hook_env = STRVEC_INIT;\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SYNC;\n \tva_list args;\n+\tconst char *arg;\n \tint ret;\n \n-\tstrvec_pushf(&hook_env, \"GIT_INDEX_FILE=%s\", index_file);\n+\tif (parallelize)\n+\t\topt.jobs = configured_hook_jobs();\n+\n+\tstrvec_pushf(&opt.env, \"GIT_INDEX_FILE=%s\", index_file);\n \n \t/*\n \t * Let the hook know that no editor will be launched.\n \t */\n \tif (!editor_is_used)\n-\t\tstrvec_push(&hook_env, \"GIT_EDITOR=:\");\n+\t\tstrvec_push(&opt.env, \"GIT_EDITOR=:\");\n \n \tva_start(args, name);\n-\tret = run_hook_ve(hook_env.v, name, args);\n+\twhile ((arg = va_arg(args, const char *)))\n+\t\tstrvec_push(&opt.args, arg);\n \tva_end(args);\n-\tstrvec_clear(&hook_env);\n+\n+\tret = run_hooks(name, &opt);\n+\trun_hooks_opt_clear(&opt);\n \n \treturn ret;\n }\ndiff --git a/commit.h b/commit.h\nindex 5467786c7b..dd33ead8e0 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -352,6 +352,7 @@ int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused)\n int compare_commits_by_gen_then_commit_date(const void *a_, const void *b_, void *unused);\n \n LAST_ARG_MUST_BE_NULL\n-int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...);\n+int run_commit_hook(int editor_is_used, int parallelize, const char *index_file,\n+\t\t    const char *name, ...);\n \n #endif /* COMMIT_H */\ndiff --git a/sequencer.c b/sequencer.c\nindex 8909a46770..5a98fd2fbc 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -34,6 +34,7 @@\n #include \"commit-reach.h\"\n #include \"rebase-interactive.h\"\n #include \"reset.h\"\n+#include \"hook.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -1203,7 +1204,7 @@ static int run_prepare_commit_msg_hook(struct repository *r,\n \t} else {\n \t\targ1 = \"message\";\n \t}\n-\tif (run_commit_hook(0, r->index_file, \"prepare-commit-msg\", name,\n+\tif (run_commit_hook(0, 0, r->index_file, \"prepare-commit-msg\", name,\n \t\t\t    arg1, arg2, NULL))\n \t\tret = error(_(\"'prepare-commit-msg' hook failed\"));\n \n@@ -1438,7 +1439,7 @@ static int try_to_commit(struct repository *r,\n \t\t}\n \t}\n \n-\tif (find_hook(\"prepare-commit-msg\")) {\n+\tif (hook_exists(\"prepare-commit-msg\", configured_hookdir_opt())) {\n \t\tres = run_prepare_commit_msg_hook(r, msg, hook_commit);\n \t\tif (res)\n \t\t\tgoto out;\n@@ -1528,7 +1529,7 @@ static int try_to_commit(struct repository *r,\n \t\tgoto out;\n \t}\n \n-\trun_commit_hook(0, r->index_file, \"post-commit\", NULL);\n+\trun_commit_hook(0, 1, r->index_file, \"post-commit\", NULL);\n \tif (flags & AMEND_MSG)\n \t\tcommit_post_rewrite(r, current_head, oid);\n \ndiff --git a/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh b/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh\nindex b3485450a2..fc93bc3d23 100755\n--- a/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh\n+++ b/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh\n@@ -5,8 +5,8 @@ test_description='pre-commit and pre-merge-commit hooks'\n . ./test-lib.sh\n \n HOOKDIR=\"$(git rev-parse --git-dir)/hooks\"\n-PRECOMMIT=\"$HOOKDIR/pre-commit\"\n-PREMERGE=\"$HOOKDIR/pre-merge-commit\"\n+PRECOMMIT=\"$(pwd)/$HOOKDIR/pre-commit\"\n+PREMERGE=\"$(pwd)/$HOOKDIR/pre-merge-commit\"\n \n # Prepare sample scripts that write their $0 to actual_hooks\n test_expect_success 'sample script setup' '\n@@ -103,6 +103,19 @@ test_expect_success 'with succeeding hook' '\n \ttest_cmp expected_hooks actual_hooks\n '\n \n+# NEEDSWORK: when 'git hook add' and 'git hook remove' have been added, use that\n+# instead\n+test_expect_success 'with succeeding hook (config-based)' '\n+\ttest_when_finished \"git config --unset hook.pre-commit.command success.sample\" &&\n+\ttest_when_finished \"rm -f expected_hooks actual_hooks\" &&\n+\tgit config hook.pre-commit.command \"$HOOKDIR/success.sample\" &&\n+\techo \"$HOOKDIR/success.sample\" >expected_hooks &&\n+\techo \"more\" >>file &&\n+\tgit add file &&\n+\tgit commit -m \"more\" &&\n+\ttest_cmp expected_hooks actual_hooks\n+'\n+\n test_expect_success 'with succeeding hook (merge)' '\n \ttest_when_finished \"rm -f \\\"$PREMERGE\\\" expected_hooks actual_hooks\" &&\n \tcp \"$HOOKDIR/success.sample\" \"$PREMERGE\" &&\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"412813","messageId":"20201222000435.1529768-4-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201222000435.1529768-1-emilyshaffer@google.com","subject":"[PATCH v3 03/17] merge: use config-based hooks for post-merge hook","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-22T00:04:21Z","receivedAt":"2020-12-22T00:06:14Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"Teach post-merge to use the hook.h library instead of the run-command.h\nlibrary to run hooks. This means that post-merge hooks can come from the\nconfig as well as from the hookdir. post-merge is invoked only from\nbuiltin/merge.c.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt | 2 ++\n builtin/merge.c            | 5 ++++-\n 2 files changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 0842cd812c..f6ddf1aa22 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -236,6 +236,8 @@ save and restore any form of metadata associated with the working tree\n (e.g.: permissions/ownership, ACLS, etc).  See contrib/hooks/setgitperms.perl\n for an example of how to do this.\n \n+Hooks executed during 'post-merge' will run in parallel by default.\n+\n pre-push\n ~~~~~~~~\n \ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex d654b6923c..717fbaa019 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -443,6 +443,7 @@ static void finish(struct commit *head_commit,\n \t\t   const struct object_id *new_head, const char *msg)\n {\n \tstruct strbuf reflog_message = STRBUF_INIT;\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;\n \tconst struct object_id *head = &head_commit->object.oid;\n \n \tif (!msg)\n@@ -484,7 +485,9 @@ static void finish(struct commit *head_commit,\n \t}\n \n \t/* Run a post-merge hook */\n-\trun_hook_le(NULL, \"post-merge\", squash ? \"1\" : \"0\", NULL);\n+\tstrvec_push(&opt.args, squash ? \"1\" : \"0\");\n+\trun_hooks(\"post-merge\", &opt);\n+\trun_hooks_opt_clear(&opt);\n \n \tapply_autostash(git_path_merge_autostash(the_repository));\n \tstrbuf_release(&reflog_message);\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"412814","messageId":"20201222000435.1529768-7-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201222000435.1529768-1-emilyshaffer@google.com","subject":"[PATCH v3 06/17] read-cache: convert post-index-change hook to use config","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-22T00:04:24Z","receivedAt":"2020-12-22T00:06:14Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"By using hook.h instead of run-command.h to run, post-index-change hooks\ncan now be specified in the config in addition to the hookdir.\npost-index-change is not run anywhere besides in read-cache.c.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt |  2 ++\n read-cache.c               | 12 +++++++++---\n 2 files changed, 11 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 5dc0690607..8249ecec5f 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -711,6 +711,8 @@ and \"0\" meaning they were not.\n Only one parameter should be set to \"1\" when the hook runs.  The hook\n running passing \"1\", \"1\" should not be possible.\n \n+Hooks run during 'post-index-change' will be run in parallel by default.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/read-cache.c b/read-cache.c\nindex ecf6f68994..dcfc080aaa 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -25,6 +25,7 @@\n #include \"fsmonitor.h\"\n #include \"thread-utils.h\"\n #include \"progress.h\"\n+#include \"hook.h\"\n \n /* Mask for the name length in ce_flags in the on-disk index */\n \n@@ -3052,6 +3053,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n \t\t\t\t unsigned flags)\n {\n \tint ret;\n+\tstruct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_ASYNC;\n \n \t/*\n \t * TODO trace2: replace \"the_repository\" with the actual repo instance\n@@ -3070,9 +3072,13 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n \telse\n \t\tret = close_lock_file_gently(lock);\n \n-\trun_hook_le(NULL, \"post-index-change\",\n-\t\t\tistate->updated_workdir ? \"1\" : \"0\",\n-\t\t\tistate->updated_skipworktree ? \"1\" : \"0\", NULL);\n+\tstrvec_pushl(&hook_opt.args,\n+\t\t     istate->updated_workdir ? \"1\" : \"0\",\n+\t\t     istate->updated_skipworktree ? \"1\" : \"0\",\n+\t\t     NULL);\n+\trun_hooks(\"post-index-change\", &hook_opt);\n+\trun_hooks_opt_clear(&hook_opt);\n+\n \tistate->updated_workdir = 0;\n \tistate->updated_skipworktree = 0;\n \n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"412815","messageId":"20201222000435.1529768-3-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201222000435.1529768-1-emilyshaffer@google.com","subject":"[PATCH v3 02/17] am: convert applypatch hooks to use config","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-22T00:04:20Z","receivedAt":"2020-12-22T00:06:14Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"Teach pre-applypatch, post-applypatch, and applypatch-msg to use the\nhook.h library instead of the run-command.h library. This enables use of\nhooks specified in the config, in addition to those in the hookdir.\nThese three hooks are called only by builtin/am.c.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt |  6 ++++++\n builtin/am.c               | 12 +++++++++---\n 2 files changed, 15 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 8b352be43f..0842cd812c 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -58,6 +58,8 @@ the message file.\n The default 'applypatch-msg' hook, when enabled, runs the\n 'commit-msg' hook, if the latter is enabled.\n \n+Hooks run during 'applypatch-msg' will not be parallelized.\n+\n pre-applypatch\n ~~~~~~~~~~~~~~\n \n@@ -73,6 +75,8 @@ make a commit if it does not pass certain test.\n The default 'pre-applypatch' hook, when enabled, runs the\n 'pre-commit' hook, if the latter is enabled.\n \n+Hooks run during 'pre-applypatch' will be run in parallel by default.\n+\n post-applypatch\n ~~~~~~~~~~~~~~~\n \n@@ -82,6 +86,8 @@ and is invoked after the patch is applied and a commit is made.\n This hook is meant primarily for notification, and cannot affect\n the outcome of `git am`.\n \n+Hooks run during 'post-applypatch' will be run in parallel by default.\n+\n pre-commit\n ~~~~~~~~~~\n \ndiff --git a/builtin/am.c b/builtin/am.c\nindex f22c73a05b..22d147bc19 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -33,6 +33,7 @@\n #include \"string-list.h\"\n #include \"packfile.h\"\n #include \"repository.h\"\n+#include \"hook.h\"\n \n /**\n  * Returns the length of the first line of msg.\n@@ -426,9 +427,12 @@ static void am_destroy(const struct am_state *state)\n static int run_applypatch_msg_hook(struct am_state *state)\n {\n \tint ret;\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SYNC;\n \n \tassert(state->msg);\n-\tret = run_hook_le(NULL, \"applypatch-msg\", am_path(state, \"final-commit\"), NULL);\n+\tstrvec_push(&opt.args, am_path(state, \"final-commit\"));\n+\tret = run_hooks(\"applypatch-msg\", &opt);\n+\trun_hooks_opt_clear(&opt);\n \n \tif (!ret) {\n \t\tFREE_AND_NULL(state->msg);\n@@ -1558,8 +1562,9 @@ static void do_commit(const struct am_state *state)\n \tstruct commit_list *parents = NULL;\n \tconst char *reflog_msg, *author, *committer = NULL;\n \tstruct strbuf sb = STRBUF_INIT;\n+\tstruct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_ASYNC;\n \n-\tif (run_hook_le(NULL, \"pre-applypatch\", NULL))\n+\tif (run_hooks(\"pre-applypatch\", &hook_opt))\n \t\texit(1);\n \n \tif (write_cache_as_tree(&tree, 0, NULL))\n@@ -1611,8 +1616,9 @@ static void do_commit(const struct am_state *state)\n \t\tfclose(fp);\n \t}\n \n-\trun_hook_le(NULL, \"post-applypatch\", NULL);\n+\trun_hooks(\"post-applypatch\", &hook_opt);\n \n+\trun_hooks_opt_clear(&hook_opt);\n \tstrbuf_release(&sb);\n }\n \n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"412816","messageId":"20201222000435.1529768-6-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201222000435.1529768-1-emilyshaffer@google.com","subject":"[PATCH v3 05/17] rebase: teach pre-rebase to use hook.h","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-22T00:04:23Z","receivedAt":"2020-12-22T00:06:14Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"By using hook.h instead of run-command.h to run hooks, pre-rebase hooks\ncan now be specified in the config as well as in the hookdir. pre-rebase\nis not called anywhere besides builtin/rebase.c.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt | 2 ++\n builtin/rebase.c           | 7 +++++--\n 2 files changed, 7 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex d74308dc20..5dc0690607 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -200,6 +200,8 @@ two parameters.  The first parameter is the upstream from which\n the series was forked.  The second parameter is the branch being\n rebased, and is not set when rebasing the current branch.\n \n+Hooks executed during 'pre-rebase' will run in parallel by default.\n+\n post-checkout\n ~~~~~~~~~~~~~\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 19c7b377aa..f61ca3e5af 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -28,6 +28,7 @@\n #include \"sequencer.h\"\n #include \"rebase-interactive.h\"\n #include \"reset.h\"\n+#include \"hook.h\"\n \n #define DEFAULT_REFLOG_ACTION \"rebase\"\n \n@@ -1312,6 +1313,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tchar *squash_onto_name = NULL;\n \tint reschedule_failed_exec = -1;\n \tint allow_preemptive_ff = 1;\n+\tstruct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_ASYNC;\n \tstruct option builtin_rebase_options[] = {\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n@@ -2024,9 +2026,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t}\n \n \t/* If a hook exists, give it a chance to interrupt*/\n+\tstrvec_pushl(&hook_opt.args, options.upstream_arg, argc ? argv[0] : NULL, NULL);\n \tif (!ok_to_skip_pre_rebase &&\n-\t    run_hook_le(NULL, \"pre-rebase\", options.upstream_arg,\n-\t\t\targc ? argv[0] : NULL, NULL))\n+\t    run_hooks(\"pre-rebase\", &hook_opt))\n \t\tdie(_(\"The pre-rebase hook refused to rebase.\"));\n \n \tif (options.flags & REBASE_DIFFSTAT) {\n@@ -2106,6 +2108,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tret = !!run_specific_rebase(&options, action);\n \n cleanup:\n+\trun_hooks_opt_clear(&hook_opt);\n \tstrbuf_release(&buf);\n \tstrbuf_release(&revisions);\n \tfree(options.head_name);\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"412818","messageId":"20201222000435.1529768-8-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201222000435.1529768-1-emilyshaffer@google.com","subject":"[PATCH v3 07/17] receive-pack: convert push-to-checkout hook to hook.h","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-22T00:04:25Z","receivedAt":"2020-12-22T00:06:14Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"By using hook.h instead of run-command.h to invoke push-to-checkout,\nhooks can now be specified in the config as well as in the hookdir.\npush-to-checkout is not called anywhere but in builtin/receive-pack.c.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt |  1 +\n builtin/receive-pack.c     | 15 +++++++++++----\n 2 files changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 8249ecec5f..8de512ee5d 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -547,6 +547,7 @@ that switches branches while\n keeping the local changes in the working tree that do not interfere\n with the difference between the branches.\n \n+Hooks executed during 'push-to-checkout' will not be parallelized.\n \n pre-auto-gc\n ~~~~~~~~~~~\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex d49d050e6e..1c0bad0448 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -29,6 +29,7 @@\n #include \"commit-reach.h\"\n #include \"worktree.h\"\n #include \"shallow.h\"\n+#include \"hook.h\"\n \n static const char * const receive_pack_usage[] = {\n \tN_(\"git receive-pack <git-dir>\"),\n@@ -1435,12 +1436,18 @@ static const char *push_to_checkout(unsigned char *hash,\n \t\t\t\t    struct strvec *env,\n \t\t\t\t    const char *work_tree)\n {\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SYNC;\n+\n \tstrvec_pushf(env, \"GIT_WORK_TREE=%s\", absolute_path(work_tree));\n-\tif (run_hook_le(env->v, push_to_checkout_hook,\n-\t\t\thash_to_hex(hash), NULL))\n+\tstrvec_pushv(&opt.env, env->v);\n+\tstrvec_push(&opt.args, hash_to_hex(hash));\n+\tif (run_hooks(push_to_checkout_hook, &opt)) {\n+\t\trun_hooks_opt_clear(&opt);\n \t\treturn \"push-to-checkout hook declined\";\n-\telse\n+\t} else {\n+\t\trun_hooks_opt_clear(&opt);\n \t\treturn NULL;\n+\t}\n }\n \n static const char *update_worktree(unsigned char *sha1, const struct worktree *worktree)\n@@ -1464,7 +1471,7 @@ static const char *update_worktree(unsigned char *sha1, const struct worktree *w\n \n \tstrvec_pushf(&env, \"GIT_DIR=%s\", absolute_path(git_dir));\n \n-\tif (!find_hook(push_to_checkout_hook))\n+\tif (!hook_exists(push_to_checkout_hook, configured_hookdir_opt()))\n \t\tretval = push_to_deploy(sha1, &env, work_tree);\n \telse\n \t\tretval = push_to_checkout(sha1, &env, work_tree);\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"412820","messageId":"20201222000435.1529768-10-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201222000435.1529768-1-emilyshaffer@google.com","subject":"[PATCH v3 09/17] hooks: convert 'post-checkout' hook to hook library","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-22T00:04:27Z","receivedAt":"2020-12-22T00:06:14Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"By using the 'hook.h' library, 'post-checkout' hooks can now be\nspecified in the config as well as in the hook directory.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt |  2 ++\n builtin/checkout.c         | 16 +++++++++++-----\n builtin/clone.c            |  7 +++++--\n builtin/worktree.c         | 30 ++++++++++++++----------------\n reset.c                    | 15 +++++++++++----\n 5 files changed, 43 insertions(+), 27 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 8de512ee5d..14035ef725 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -224,6 +224,8 @@ This hook can be used to perform repository validity checks, auto-display\n differences from the previous HEAD if different, or set working dir metadata\n properties.\n \n+Hooks executed during 'post-checkout' will not be parallelized.\n+\n post-merge\n ~~~~~~~~~~\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 9b82119129..20966452b8 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -9,6 +9,7 @@\n #include \"config.h\"\n #include \"diff.h\"\n #include \"dir.h\"\n+#include \"hook.h\"\n #include \"ll-merge.h\"\n #include \"lockfile.h\"\n #include \"merge-recursive.h\"\n@@ -104,13 +105,18 @@ struct branch_info {\n static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,\n \t\t\t      int changed)\n {\n-\treturn run_hook_le(NULL, \"post-checkout\",\n-\t\t\t   oid_to_hex(old_commit ? &old_commit->object.oid : &null_oid),\n-\t\t\t   oid_to_hex(new_commit ? &new_commit->object.oid : &null_oid),\n-\t\t\t   changed ? \"1\" : \"0\", NULL);\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SYNC;\n+\tint rc;\n \t/* \"new_commit\" can be NULL when checking out from the index before\n \t   a commit exists. */\n-\n+\tstrvec_pushl(&opt.args,\n+\t\t     oid_to_hex(old_commit ? &old_commit->object.oid : &null_oid),\n+\t\t     oid_to_hex(new_commit ? &new_commit->object.oid : &null_oid),\n+\t\t     changed ? \"1\" : \"0\",\n+\t\t     NULL);\n+\trc = run_hooks(\"post-checkout\", &opt);\n+\trun_hooks_opt_clear(&opt);\n+\treturn rc;\n }\n \n static int update_some(const struct object_id *oid, struct strbuf *base,\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex a0841923cf..307336e576 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -32,6 +32,7 @@\n #include \"connected.h\"\n #include \"packfile.h\"\n #include \"list-objects-filter-options.h\"\n+#include \"hook.h\"\n \n /*\n  * Overall FIXMEs:\n@@ -771,6 +772,7 @@ static int checkout(int submodule_progress)\n \tstruct tree *tree;\n \tstruct tree_desc t;\n \tint err = 0;\n+\tstruct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_SYNC;\n \n \tif (option_no_checkout)\n \t\treturn 0;\n@@ -816,8 +818,9 @@ static int checkout(int submodule_progress)\n \tif (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))\n \t\tdie(_(\"unable to write new index file\"));\n \n-\terr |= run_hook_le(NULL, \"post-checkout\", oid_to_hex(&null_oid),\n-\t\t\t   oid_to_hex(&oid), \"1\", NULL);\n+\tstrvec_pushl(&hook_opt.args, oid_to_hex(&null_oid), oid_to_hex(&oid), \"1\", NULL);\n+\terr |= run_hooks(\"post-checkout\", &hook_opt);\n+\trun_hooks_opt_clear(&hook_opt);\n \n \tif (!err && (option_recurse_submodules.nr > 0)) {\n \t\tstruct strvec args = STRVEC_INIT;\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 197fd24a55..9a87c4c120 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -12,6 +12,7 @@\n #include \"submodule.h\"\n #include \"utf8.h\"\n #include \"worktree.h\"\n+#include \"hook.h\"\n \n static const char * const worktree_usage[] = {\n \tN_(\"git worktree add [<options>] <path> [<commit-ish>]\"),\n@@ -455,22 +456,19 @@ static int add_worktree(const char *path, const char *refname,\n \t * is_junk is cleared, but do return appropriate code when hook fails.\n \t */\n \tif (!ret && opts->checkout) {\n-\t\tconst char *hook = find_hook(\"post-checkout\");\n-\t\tif (hook) {\n-\t\t\tconst char *env[] = { \"GIT_DIR\", \"GIT_WORK_TREE\", NULL };\n-\t\t\tcp.git_cmd = 0;\n-\t\t\tcp.no_stdin = 1;\n-\t\t\tcp.stdout_to_stderr = 1;\n-\t\t\tcp.dir = path;\n-\t\t\tcp.env = env;\n-\t\t\tcp.argv = NULL;\n-\t\t\tcp.trace2_hook_name = \"post-checkout\";\n-\t\t\tstrvec_pushl(&cp.args, absolute_path(hook),\n-\t\t\t\t     oid_to_hex(&null_oid),\n-\t\t\t\t     oid_to_hex(&commit->object.oid),\n-\t\t\t\t     \"1\", NULL);\n-\t\t\tret = run_command(&cp);\n-\t\t}\n+\t\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SYNC;\n+\n+\t\tstrvec_pushl(&opt.env, \"GIT_DIR\", \"GIT_WORK_TREE\", NULL);\n+\t\tstrvec_pushl(&opt.args,\n+\t\t\t     oid_to_hex(&null_oid),\n+\t\t\t     oid_to_hex(&commit->object.oid),\n+\t\t\t     \"1\",\n+\t\t\t     NULL);\n+\t\topt.dir = path;\n+\n+\t\tret = run_hooks(\"post-checkout\", &opt);\n+\n+\t\trun_hooks_opt_clear(&opt);\n \t}\n \n \tstrvec_clear(&child_env);\ndiff --git a/reset.c b/reset.c\nindex 2f4fbd07c5..e6bfaf67e1 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -7,6 +7,7 @@\n #include \"tree-walk.h\"\n #include \"tree.h\"\n #include \"unpack-trees.h\"\n+#include \"hook.h\"\n \n int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \t       const char *switch_to_branch, unsigned flags,\n@@ -126,10 +127,16 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \t\t\tret = create_symref(\"HEAD\", switch_to_branch,\n \t\t\t\t\t    reflog_head);\n \t}\n-\tif (run_hook)\n-\t\trun_hook_le(NULL, \"post-checkout\",\n-\t\t\t    oid_to_hex(orig ? orig : &null_oid),\n-\t\t\t    oid_to_hex(oid), \"1\", NULL);\n+\tif (run_hook) {\n+\t\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_SYNC;\n+\t\tstrvec_pushl(&opt.args,\n+\t\t\t     oid_to_hex(orig ? orig : &null_oid),\n+\t\t\t     oid_to_hex(oid),\n+\t\t\t     \"1\",\n+\t\t\t     NULL);\n+\t\trun_hooks(\"post-checkout\", &opt);\n+\t\trun_hooks_opt_clear(&opt);\n+\t}\n \n leave_reset_head:\n \tstrbuf_release(&msg);\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"412817","messageId":"20201222000435.1529768-9-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201222000435.1529768-1-emilyshaffer@google.com","subject":"[PATCH v3 08/17] git-p4: use 'git hook' to run hooks","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-22T00:04:26Z","receivedAt":"2020-12-22T00:06:15Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"Instead of duplicating the behavior of run-command.h:run_hook_le() in\nPython, we can directly call 'git hook run'. As a bonus, this means\ngit-p4 learns how to find hook specifications from the Git config as\nwell as from the hookdir.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n\nNotes:\n    Maybe there is a better way to do this - I had a hard time getting this to run\n    locally, and Python is not my forte, so if anybody has a better approach I'd\n    love to just take that patch instead :)\n    \n    Since v6, removed the developer debug print statements.... :X\n    \n    Maybe there is a better way to do this - I had a hard time getting this to run\n    locally, and Python is not my forte, so if anybody has a better approach I'd\n    love to just take that patch instead :)\n\n git-p4.py | 67 +++++--------------------------------------------------\n 1 file changed, 6 insertions(+), 61 deletions(-)\n\ndiff --git a/git-p4.py b/git-p4.py\nindex 6ae5bbfe99..6e23e2ad1a 100755\n--- a/git-p4.py\n+++ b/git-p4.py\n@@ -208,70 +208,15 @@ def decode_path(path):\n \n def run_git_hook(cmd, param=[]):\n     \"\"\"Execute a hook if the hook exists.\"\"\"\n-    if verbose:\n-        sys.stderr.write(\"Looking for hook: %s\\n\" % cmd)\n-        sys.stderr.flush()\n-\n-    hooks_path = gitConfig(\"core.hooksPath\")\n-    if len(hooks_path) <= 0:\n-        hooks_path = os.path.join(os.environ[\"GIT_DIR\"], \"hooks\")\n-\n-    if not isinstance(param, list):\n-        param=[param]\n-\n-    # resolve hook file name, OS depdenent\n-    hook_file = os.path.join(hooks_path, cmd)\n-    if platform.system() == 'Windows':\n-        if not os.path.isfile(hook_file):\n-            # look for the file with an extension\n-            files = glob.glob(hook_file + \".*\")\n-            if not files:\n-                return True\n-            files.sort()\n-            hook_file = files.pop()\n-            while hook_file.upper().endswith(\".SAMPLE\"):\n-                # The file is a sample hook. We don't want it\n-                if len(files) > 0:\n-                    hook_file = files.pop()\n-                else:\n-                    return True\n-\n-    if not os.path.isfile(hook_file) or not os.access(hook_file, os.X_OK):\n+    if not cmd:\n         return True\n \n-    return run_hook_command(hook_file, param) == 0\n-\n-def run_hook_command(cmd, param):\n-    \"\"\"Executes a git hook command\n-       cmd = the command line file to be executed. This can be\n-       a file that is run by OS association.\n-\n-       param = a list of parameters to pass to the cmd command\n-\n-       On windows, the extension is checked to see if it should\n-       be run with the Git for Windows Bash shell.  If there\n-       is no file extension, the file is deemed a bash shell\n-       and will be handed off to sh.exe. Otherwise, Windows\n-       will be called with the shell to handle the file assocation.\n-\n-       For non Windows operating systems, the file is called\n-       as an executable.\n-    \"\"\"\n-    cli = [cmd] + param\n-    use_shell = False\n-    if platform.system() == 'Windows':\n-        (root,ext) = os.path.splitext(cmd)\n-        if ext == \"\":\n-            exe_path = os.environ.get(\"EXEPATH\")\n-            if exe_path is None:\n-                exe_path = \"\"\n-            else:\n-                exe_path = os.path.join(exe_path, \"bin\")\n-            cli = [os.path.join(exe_path, \"SH.EXE\")] + cli\n-        else:\n-            use_shell = True\n-    return subprocess.call(cli, shell=use_shell)\n+    \"\"\"args are specified with -a <arg> -a <arg> -a <arg>\"\"\"\n+    args = (['git', 'hook', 'run'] +\n+\t    [\"-a\" + arg for arg in param] +\n+\t    [cmd])\n \n+    return subprocess.call(args) == 0\n \n def write_pipe(c, stdin):\n     if verbose:\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"412819","messageId":"20201222000435.1529768-14-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201222000435.1529768-1-emilyshaffer@google.com","subject":"[PATCH v3 13/17] receive-pack: convert 'update' hook to hook.h","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-22T00:04:31Z","receivedAt":"2020-12-22T00:06:15Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"By using hook.h to invoke the 'update' hook, now hooks can be specified\nin the config in addition to the hookdir.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt       |  2 +\n builtin/receive-pack.c           | 65 +++++++++++++++++++++-----------\n t/t1416-ref-transaction-hooks.sh |  4 +-\n 3 files changed, 48 insertions(+), 23 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 3a35500132..2b3a74f249 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -359,6 +359,8 @@ 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+Hooks executed during 'update' are run in parallel by default.\n+\n [[proc-receive]]\n proc-receive\n ~~~~~~~~~~~~\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 1c0bad0448..57f3bb979c 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -938,33 +938,56 @@ static int run_receive_hook(struct command *commands,\n \treturn status;\n }\n \n-static int run_update_hook(struct command *cmd)\n+static void hook_output_to_sideband(struct strbuf *output, void *cb_data)\n {\n-\tconst char *argv[5];\n-\tstruct child_process proc = CHILD_PROCESS_INIT;\n-\tint code;\n+\tint keepalive_active = 0;\n \n-\targv[0] = find_hook(\"update\");\n-\tif (!argv[0])\n-\t\treturn 0;\n+\tif (keepalive_in_sec <= 0)\n+\t\tuse_keepalive = KEEPALIVE_NEVER;\n+\tif (use_keepalive == KEEPALIVE_ALWAYS)\n+\t\tkeepalive_active = 1;\n \n-\targv[1] = cmd->ref_name;\n-\targv[2] = oid_to_hex(&cmd->old_oid);\n-\targv[3] = oid_to_hex(&cmd->new_oid);\n-\targv[4] = NULL;\n+\t/* send a keepalive if there is no data to write */\n+\tif (keepalive_active && !output->len) {\n+\t\tstatic const char buf[] = \"0005\\1\";\n+\t\twrite_or_die(1, buf, sizeof(buf) - 1);\n+\t\treturn;\n+\t}\n \n-\tproc.no_stdin = 1;\n-\tproc.stdout_to_stderr = 1;\n-\tproc.err = use_sideband ? -1 : 0;\n-\tproc.argv = argv;\n-\tproc.trace2_hook_name = \"update\";\n+\tif (use_keepalive == KEEPALIVE_AFTER_NUL && !keepalive_active) {\n+\t\tconst char *first_null = memchr(output->buf, '\\0', output->len);\n+\t\tif (first_null) {\n+\t\t\t/* The null bit is excluded. */\n+\t\t\tsize_t before_null = first_null - output->buf;\n+\t\t\tsize_t after_null = output->len - (before_null + 1);\n+\t\t\tkeepalive_active = 1;\n+\t\t\tsend_sideband(1, 2, output->buf, before_null, use_sideband);\n+\t\t\tsend_sideband(1, 2, first_null + 1, after_null, use_sideband);\n+\n+\t\t\treturn;\n+\t\t}\n+\t}\n+\n+\tsend_sideband(1, 2, output->buf, output->len, use_sideband);\n+}\n+\n+static int run_update_hook(struct command *cmd)\n+{\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;\n+\tint code;\n+\n+\tstrvec_pushl(&opt.args,\n+\t\t     cmd->ref_name,\n+\t\t     oid_to_hex(&cmd->old_oid),\n+\t\t     oid_to_hex(&cmd->new_oid),\n+\t\t     NULL);\n \n-\tcode = start_command(&proc);\n-\tif (code)\n-\t\treturn code;\n \tif (use_sideband)\n-\t\tcopy_to_sideband(proc.err, -1, NULL);\n-\treturn finish_command(&proc);\n+\t\topt.consume_sideband = hook_output_to_sideband;\n+\n+\tcode = run_hooks(\"update\", &opt);\n+\trun_hooks_opt_clear(&opt);\n+\treturn code;\n }\n \n static struct command *find_command_by_refname(struct command *list,\ndiff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh\nindex 17f11f5cb0..28359f6099 100755\n--- a/t/t1416-ref-transaction-hooks.sh\n+++ b/t/t1416-ref-transaction-hooks.sh\n@@ -121,10 +121,10 @@ test_expect_success 'interleaving hook calls succeed' '\n \tEOF\n \n \tcat >expect <<-EOF &&\n-\t\thooks/update refs/tags/PRE $ZERO_OID $PRE_OID\n+\t\t$(pwd)/target-repo.git/hooks/update refs/tags/PRE $ZERO_OID $PRE_OID\n \t\t$(pwd)/target-repo.git/hooks/reference-transaction prepared\n \t\t$(pwd)/target-repo.git/hooks/reference-transaction committed\n-\t\thooks/update refs/tags/POST $ZERO_OID $POST_OID\n+\t\t$(pwd)/target-repo.git/hooks/update refs/tags/POST $ZERO_OID $POST_OID\n \t\t$(pwd)/target-repo.git/hooks/reference-transaction prepared\n \t\t$(pwd)/target-repo.git/hooks/reference-transaction committed\n \tEOF\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"412821","messageId":"20201222000435.1529768-15-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201222000435.1529768-1-emilyshaffer@google.com","subject":"[PATCH v3 14/17] proc-receive: acquire hook list from hook.h","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-22T00:04:32Z","receivedAt":"2020-12-22T00:06:15Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"The proc-receive hook differs from most other hooks Git invokes because\nthe hook and the parent Git process engage in bidirectional\ncommunication via stdin/stdout. This bidirectional communication is\nunsuitable for multiple hooks, whether they are in series or in\nparallel, and is incompatible with run-command.h:run_processes_parallel:\n\n- The proc-receive hook is intended to modify the state of the Git repo.\n  From 'git help githooks':\n    This [proc-receive] hook is responsible for updating the relevant\n    references and reporting the results back to 'receive-pack'.\n  This prevents parallelization and implies, at least, specific ordering\n  of hook execution.\n- The proc-receive hook can reject a push by aborting early with an\n  error code. If a former hook ran through the entire push contents\n  successfully but a later hook rejects some of the push, the repo may\n  be left in a partially-updated (and corrupt) state.\n- The callback model of the run_processes_parallel() API is unsuited to\n  the current implementation of proc-receive, which loops through\n  \"send-receive-consider\" with the child process. proc-receive today\n  relies on stateful communication with the child process, which would be\n  unwieldy to implement with callbacks and saved state.\n- Additionally, run_processes_parallel() is designed to collate the\n  output of many child processes into a single output (stderr or callback),\n  and would require significant work to tell the caller which process sent\n  the output, and indeed to collect any output before the child process\n  has exited.\n\nSo, rather than using hook.h:run_hooks() to invoke the proc-receive\nhook, receive-pack.c can learn to ask hook.h:hook_list() for the\nlocation of a hook to run. This allows users to configure their\nproc-receive in a global config for all repos if they want, or a local\nconfig if they just don't want to use the hookdir. Because running more\nthan one proc-receive hook doesn't make sense from a repo state\nperspective, we can explicitly ban configuring more than one\nproc-receive hook at a time.\n\nIf a user wants to globally configure one proc-receive hook for most of\ntheir repos, but override that hook in a single repo, they should use\n'skip' to manually remove the global hook in their special repo:\n\n~/.gitconfig:\n[hook.proc-receive]\n  command = /usr/bin/usual-proc-receive\n\n~/special-repo/.git/config:\n[hookcmd./usr/bin/usual-proc-receive]\n  skip = true\n[hook.proc-receive]\n  command = /usr/bin/special-proc-receive\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt                |  4 ++\n builtin/receive-pack.c                    | 33 +++++++++++++++-\n t/t5411/test-0015-too-many-hooks-error.sh | 47 +++++++++++++++++++++++\n 3 files changed, 82 insertions(+), 2 deletions(-)\n create mode 100644 t/t5411/test-0015-too-many-hooks-error.sh\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 2b3a74f249..2c59c537f9 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -423,6 +423,10 @@ 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+It is forbidden to specify more than one hook for 'proc-receive'. If a\n+globally-configured 'proc-receive' must be overridden, use\n+'hookcmd.<global-hook>.skip = true' to ignore it.\n+\n [[post-receive]]\n post-receive\n ~~~~~~~~~~~~\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 57f3bb979c..4a6ad27404 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1145,11 +1145,40 @@ static int run_proc_receive_hook(struct command *commands,\n \tint version = 0;\n \tint code;\n \n-\targv[0] = find_hook(\"proc-receive\");\n-\tif (!argv[0]) {\n+\tstruct strbuf hookname = STRBUF_INIT;\n+\tstruct hook *proc_receive = NULL;\n+\tstruct list_head *pos, *hooks;\n+\n+\tstrbuf_addstr(&hookname, \"proc-receive\");\n+\thooks = hook_list(&hookname);\n+\n+\tlist_for_each(pos, hooks) {\n+\t\tif (proc_receive) {\n+\t\t\trp_error(\"only one 'proc-receive' hook can be specified\");\n+\t\t\treturn -1;\n+\t\t}\n+\t\tproc_receive = list_entry(pos, struct hook, list);\n+\t\t/* check if the hookdir hook should be ignored */\n+\t\tif (proc_receive->from_hookdir) {\n+\t\t\tswitch (configured_hookdir_opt()) {\n+\t\t\tcase HOOKDIR_INTERACTIVE:\n+\t\t\tcase HOOKDIR_NO:\n+\t\t\t\tproc_receive = NULL;\n+\t\t\t\tbreak;\n+\t\t\tdefault:\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t}\n+\n+\tif (!proc_receive) {\n \t\trp_error(\"cannot find hook 'proc-receive'\");\n \t\treturn -1;\n \t}\n+\n+\n+\targv[0] = proc_receive->command.buf;\n \targv[1] = NULL;\n \n \tproc.argv = argv;\ndiff --git a/t/t5411/test-0015-too-many-hooks-error.sh b/t/t5411/test-0015-too-many-hooks-error.sh\nnew file mode 100644\nindex 0000000000..2d64534510\n--- /dev/null\n+++ b/t/t5411/test-0015-too-many-hooks-error.sh\n@@ -0,0 +1,47 @@\n+test_expect_success \"setup too  many proc-receive hooks (ok, $PROTOCOL)\" '\n+\twrite_script \"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+\tgit -C \"$upstream\" config --add \"hook.proc-receive.command\" proc-receive &&\n+\tcp proc-receive \"$upstream/hooks/proc-receive\"\n+'\n+\n+# Refs of upstream : main(A)\n+# Refs of workbench: main(A)  tags/v123\n+# git push         :                       next(A)  refs/for/main/topic(A)\n+test_expect_success \"proc-receive: reject more than one configured hook\" '\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+\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: error: only one \"proc-receive\" hook can be specified\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 (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/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+# Refs of workbench: main(A)  tags/v123\n+test_expect_success \"cleanup ($PROTOCOL)\" '\n+\tgit -C \"$upstream\" config --unset \"hook.proc-receive.command\" \"proc-receive\" &&\n+\tgit -C \"$upstream\" update-ref -d refs/heads/next\n+'\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"412822","messageId":"20201222000435.1529768-11-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201222000435.1529768-1-emilyshaffer@google.com","subject":"[PATCH v3 10/17] hook: convert 'post-rewrite' hook to config","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-22T00:04:28Z","receivedAt":"2020-12-22T00:06:15Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"By using 'hook.h' for 'post-rewrite', we simplify hook invocations by\nnot needing to put together our own 'struct child_process' and we also\nlearn to run hooks specified in the config as well as the hook dir.\n\nThe signal handling that's being removed by this commit now takes place\nin run-command.h:run_processes_parallel(), so it is OK to remove them\nhere.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt |  2 +\n builtin/am.c               | 18 +++------\n sequencer.c                | 76 +++++++++++++++-----------------------\n 3 files changed, 36 insertions(+), 60 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 14035ef725..db290984f6 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -585,6 +585,8 @@ The hook always runs after the automatic note copying (see\n \"notes.rewrite.<command>\" in linkgit:git-config[1]) has happened, and\n thus has access to these notes.\n \n+Hooks run during 'post-rewrite' will be run in parallel by default.\n+\n The following command-specific comments apply:\n \n rebase::\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 22d147bc19..c7cad2cb32 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -449,23 +449,15 @@ static int run_applypatch_msg_hook(struct am_state *state)\n  */\n static int run_post_rewrite_hook(const struct am_state *state)\n {\n-\tstruct child_process cp = CHILD_PROCESS_INIT;\n-\tconst char *hook = find_hook(\"post-rewrite\");\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;\n \tint ret;\n \n-\tif (!hook)\n-\t\treturn 0;\n-\n-\tstrvec_push(&cp.args, hook);\n-\tstrvec_push(&cp.args, \"rebase\");\n+\tstrvec_push(&opt.args, \"rebase\");\n+\topt.path_to_stdin = am_path(state, \"rewritten\");\n \n-\tcp.in = xopen(am_path(state, \"rewritten\"), O_RDONLY);\n-\tcp.stdout_to_stderr = 1;\n-\tcp.trace2_hook_name = \"post-rewrite\";\n+\tret = run_hooks(\"post-rewrite\", &opt);\n \n-\tret = run_command(&cp);\n-\n-\tclose(cp.in);\n+\trun_hooks_opt_clear(&opt);\n \treturn ret;\n }\n \ndiff --git a/sequencer.c b/sequencer.c\nindex 5a98fd2fbc..4befd862ff 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -35,6 +35,7 @@\n #include \"rebase-interactive.h\"\n #include \"reset.h\"\n #include \"hook.h\"\n+#include \"string-list.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -1143,33 +1144,23 @@ int update_head_with_reflog(const struct commit *old_head,\n static int run_rewrite_hook(const struct object_id *oldoid,\n \t\t\t    const struct object_id *newoid)\n {\n-\tstruct child_process proc = CHILD_PROCESS_INIT;\n-\tconst char *argv[3];\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;\n+\tstruct strbuf tmp = STRBUF_INIT;\n \tint code;\n-\tstruct strbuf sb = STRBUF_INIT;\n \n-\targv[0] = find_hook(\"post-rewrite\");\n-\tif (!argv[0])\n-\t\treturn 0;\n+\tstrvec_push(&opt.args, \"amend\");\n \n-\targv[1] = \"amend\";\n-\targv[2] = NULL;\n-\n-\tproc.argv = argv;\n-\tproc.in = -1;\n-\tproc.stdout_to_stderr = 1;\n-\tproc.trace2_hook_name = \"post-rewrite\";\n-\n-\tcode = start_command(&proc);\n-\tif (code)\n-\t\treturn code;\n-\tstrbuf_addf(&sb, \"%s %s\\n\", oid_to_hex(oldoid), oid_to_hex(newoid));\n-\tsigchain_push(SIGPIPE, SIG_IGN);\n-\twrite_in_full(proc.in, sb.buf, sb.len);\n-\tclose(proc.in);\n-\tstrbuf_release(&sb);\n-\tsigchain_pop(SIGPIPE);\n-\treturn finish_command(&proc);\n+\tstrbuf_addf(&tmp,\n+\t\t    \"%s %s\",\n+\t\t    oid_to_hex(oldoid),\n+\t\t    oid_to_hex(newoid));\n+\tstring_list_append(&opt.str_stdin, tmp.buf);\n+\n+\tcode = run_hooks(\"post-rewrite\", &opt);\n+\n+\trun_hooks_opt_clear(&opt);\n+\tstrbuf_release(&tmp);\n+\treturn code;\n }\n \n void commit_post_rewrite(struct repository *r,\n@@ -4317,30 +4308,21 @@ static int pick_commits(struct repository *r,\n \t\tflush_rewritten_pending();\n \t\tif (!stat(rebase_path_rewritten_list(), &st) &&\n \t\t\t\tst.st_size > 0) {\n-\t\t\tstruct child_process child = CHILD_PROCESS_INIT;\n-\t\t\tconst char *post_rewrite_hook =\n-\t\t\t\tfind_hook(\"post-rewrite\");\n-\n-\t\t\tchild.in = open(rebase_path_rewritten_list(), O_RDONLY);\n-\t\t\tchild.git_cmd = 1;\n-\t\t\tstrvec_push(&child.args, \"notes\");\n-\t\t\tstrvec_push(&child.args, \"copy\");\n-\t\t\tstrvec_push(&child.args, \"--for-rewrite=rebase\");\n+\t\t\tstruct child_process notes_cp = CHILD_PROCESS_INIT;\n+\t\t\tstruct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_ASYNC;\n+\n+\t\t\tnotes_cp.in = open(rebase_path_rewritten_list(), O_RDONLY);\n+\t\t\tnotes_cp.git_cmd = 1;\n+\t\t\tstrvec_push(&notes_cp.args, \"notes\");\n+\t\t\tstrvec_push(&notes_cp.args, \"copy\");\n+\t\t\tstrvec_push(&notes_cp.args, \"--for-rewrite=rebase\");\n \t\t\t/* we don't care if this copying failed */\n-\t\t\trun_command(&child);\n-\n-\t\t\tif (post_rewrite_hook) {\n-\t\t\t\tstruct child_process hook = CHILD_PROCESS_INIT;\n-\n-\t\t\t\thook.in = open(rebase_path_rewritten_list(),\n-\t\t\t\t\tO_RDONLY);\n-\t\t\t\thook.stdout_to_stderr = 1;\n-\t\t\t\thook.trace2_hook_name = \"post-rewrite\";\n-\t\t\t\tstrvec_push(&hook.args, post_rewrite_hook);\n-\t\t\t\tstrvec_push(&hook.args, \"rebase\");\n-\t\t\t\t/* we don't care if this hook failed */\n-\t\t\t\trun_command(&hook);\n-\t\t\t}\n+\t\t\trun_command(&notes_cp);\n+\n+\t\t\thook_opt.path_to_stdin = rebase_path_rewritten_list();\n+\t\t\tstrvec_push(&hook_opt.args, \"rebase\");\n+\t\t\trun_hooks(\"post-rewrite\", &hook_opt);\n+\t\t\trun_hooks_opt_clear(&hook_opt);\n \t\t}\n \t\tapply_autostash(rebase_path_autostash());\n \n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"412824","messageId":"20201222000435.1529768-17-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201222000435.1529768-1-emilyshaffer@google.com","subject":"[PATCH v3 16/17] receive-pack: convert receive hooks to hook.h","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-22T00:04:34Z","receivedAt":"2020-12-22T00:06:15Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"By using the hook.h library to run receive hooks, they can be specified\nin the config as well as in the hookdir.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt |   4 +\n builtin/receive-pack.c     | 197 +++++++++++++++++--------------------\n 2 files changed, 94 insertions(+), 107 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 1412cd5846..c450f7a27e 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -314,6 +314,8 @@ will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.\n See the section on \"Quarantine Environment\" in\n linkgit:git-receive-pack[1] for some caveats.\n \n+Hooks executed during 'pre-receive' will not be parallelized.\n+\n [[update]]\n update\n ~~~~~~\n@@ -466,6 +468,8 @@ environment variables will not be set. If the client selects\n to use push options, but doesn't transmit any, the count variable\n will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.\n \n+Hooks executed during 'post-receive' are run in parallel by default.\n+\n [[post-update]]\n post-update\n ~~~~~~~~~~~\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 5c23d5ed86..348d5b0a9b 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -748,7 +748,7 @@ static int check_cert_push_options(const struct string_list *push_options)\n \treturn retval;\n }\n \n-static void prepare_push_cert_sha1(struct child_process *proc)\n+static void prepare_push_cert_sha1(struct run_hooks_opt *opt)\n {\n \tstatic int already_done;\n \n@@ -772,110 +772,42 @@ static void prepare_push_cert_sha1(struct child_process *proc)\n \t\tnonce_status = check_nonce(push_cert.buf, bogs);\n \t}\n \tif (!is_null_oid(&push_cert_oid)) {\n-\t\tstrvec_pushf(&proc->env_array, \"GIT_PUSH_CERT=%s\",\n+\t\tstrvec_pushf(&opt->env, \"GIT_PUSH_CERT=%s\",\n \t\t\t     oid_to_hex(&push_cert_oid));\n-\t\tstrvec_pushf(&proc->env_array, \"GIT_PUSH_CERT_SIGNER=%s\",\n+\t\tstrvec_pushf(&opt->env, \"GIT_PUSH_CERT_SIGNER=%s\",\n \t\t\t     sigcheck.signer ? sigcheck.signer : \"\");\n-\t\tstrvec_pushf(&proc->env_array, \"GIT_PUSH_CERT_KEY=%s\",\n+\t\tstrvec_pushf(&opt->env, \"GIT_PUSH_CERT_KEY=%s\",\n \t\t\t     sigcheck.key ? sigcheck.key : \"\");\n-\t\tstrvec_pushf(&proc->env_array, \"GIT_PUSH_CERT_STATUS=%c\",\n+\t\tstrvec_pushf(&opt->env, \"GIT_PUSH_CERT_STATUS=%c\",\n \t\t\t     sigcheck.result);\n \t\tif (push_cert_nonce) {\n-\t\t\tstrvec_pushf(&proc->env_array,\n+\t\t\tstrvec_pushf(&opt->env,\n \t\t\t\t     \"GIT_PUSH_CERT_NONCE=%s\",\n \t\t\t\t     push_cert_nonce);\n-\t\t\tstrvec_pushf(&proc->env_array,\n+\t\t\tstrvec_pushf(&opt->env,\n \t\t\t\t     \"GIT_PUSH_CERT_NONCE_STATUS=%s\",\n \t\t\t\t     nonce_status);\n \t\t\tif (nonce_status == NONCE_SLOP)\n-\t\t\t\tstrvec_pushf(&proc->env_array,\n+\t\t\t\tstrvec_pushf(&opt->env,\n \t\t\t\t\t     \"GIT_PUSH_CERT_NONCE_SLOP=%ld\",\n \t\t\t\t\t     nonce_stamp_slop);\n \t\t}\n \t}\n }\n \n+struct receive_hook_feed_context {\n+\tstruct command *cmd;\n+\tint skip_broken;\n+};\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 };\n \n-typedef int (*feed_fn)(void *, const char **, size_t *);\n-static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n-\t\t\t     struct receive_hook_feed_state *feed_state)\n-{\n-\tstruct child_process proc = CHILD_PROCESS_INIT;\n-\tstruct async muxer;\n-\tconst char *argv[2];\n-\tint code;\n-\n-\targv[0] = find_hook(hook_name);\n-\tif (!argv[0])\n-\t\treturn 0;\n-\n-\targv[1] = NULL;\n-\n-\tproc.argv = argv;\n-\tproc.in = -1;\n-\tproc.stdout_to_stderr = 1;\n-\tproc.trace2_hook_name = hook_name;\n-\n-\tif (feed_state->push_options) {\n-\t\tint i;\n-\t\tfor (i = 0; i < feed_state->push_options->nr; i++)\n-\t\t\tstrvec_pushf(&proc.env_array,\n-\t\t\t\t     \"GIT_PUSH_OPTION_%d=%s\", i,\n-\t\t\t\t     feed_state->push_options->items[i].string);\n-\t\tstrvec_pushf(&proc.env_array, \"GIT_PUSH_OPTION_COUNT=%d\",\n-\t\t\t     feed_state->push_options->nr);\n-\t} else\n-\t\tstrvec_pushf(&proc.env_array, \"GIT_PUSH_OPTION_COUNT\");\n-\n-\tif (tmp_objdir)\n-\t\tstrvec_pushv(&proc.env_array, tmp_objdir_env(tmp_objdir));\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}\n-\n-\tprepare_push_cert_sha1(&proc);\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-\twhile (1) {\n-\t\tconst char *buf;\n-\t\tsize_t n;\n-\t\tif (feed(feed_state, &buf, &n))\n-\t\t\tbreak;\n-\t\tif (write_in_full(proc.in, buf, n) < 0)\n-\t\t\tbreak;\n-\t}\n-\tclose(proc.in);\n-\tif (use_sideband)\n-\t\tfinish_async(&muxer);\n-\n-\tsigchain_pop(SIGPIPE);\n-\n-\treturn finish_command(&proc);\n-}\n-\n-static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n+static int feed_receive_hook(void *state_)\n {\n \tstruct receive_hook_feed_state *state = state_;\n \tstruct command *cmd = state->cmd;\n@@ -884,9 +816,7 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\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+\t\treturn 1; /* EOF - close the pipe*/\n \tstrbuf_reset(&state->buf);\n \tif (!state->report)\n \t\tstate->report = cmd->report;\n@@ -910,32 +840,36 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\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-\t}\n \treturn 0;\n }\n \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    const struct string_list *push_options)\n+static int feed_receive_hook_cb(struct strbuf *pipe, void *pp_cb, void *pp_task_cb)\n {\n-\tstruct receive_hook_feed_state state;\n-\tint status;\n-\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-\tstate.push_options = push_options;\n-\tstatus = run_and_feed_hook(hook_name, feed_receive_hook, &state);\n-\tstrbuf_release(&state.buf);\n-\treturn status;\n+\tstruct hook *hook = pp_task_cb;\n+\tstruct receive_hook_feed_state *feed_state = hook->feed_pipe_cb_data;\n+\tint rc;\n+\n+\t/* first-time setup */\n+\tif (!feed_state) {\n+\t\tstruct hook_cb_data *hook_cb = pp_cb;\n+\t\tstruct run_hooks_opt *opt = hook_cb->options;\n+\t\tstruct receive_hook_feed_context *ctx = opt->feed_pipe_ctx;\n+\t\tif (!ctx)\n+\t\t\tBUG(\"run_hooks_opt.feed_pipe_ctx required for receive hook\");\n+\n+\t\tfeed_state = xmalloc(sizeof(struct receive_hook_feed_state));\n+\t\tstrbuf_init(&feed_state->buf, 0);\n+\t\tfeed_state->cmd = ctx->cmd;\n+\t\tfeed_state->skip_broken = ctx->skip_broken;\n+\t\tfeed_state->report = NULL;\n+\n+\t\thook->feed_pipe_cb_data = feed_state;\n+\t}\n+\n+\trc = feed_receive_hook(feed_state);\n+\tif (!rc)\n+\t\tstrbuf_addbuf(pipe, &feed_state->buf);\n+\treturn rc;\n }\n \n static void hook_output_to_sideband(struct strbuf *output, void *cb_data)\n@@ -971,6 +905,55 @@ static void hook_output_to_sideband(struct strbuf *output, void *cb_data)\n \tsend_sideband(1, 2, output->buf, output->len, use_sideband);\n }\n \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    const struct string_list *push_options)\n+{\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;\n+\tstruct receive_hook_feed_context ctx;\n+\tint rc;\n+\tstruct command *iter = commands;\n+\n+\t/* if there are no valid commands, don't invoke the hook at all. */\n+\twhile (iter && skip_broken && (iter->error_string || iter->did_not_exist))\n+\t\titer = iter->next;\n+\tif (!iter)\n+\t\treturn 0;\n+\n+\t/* pre-receive hooks should run in series as the hook updates refs */\n+\tif (!strcmp(hook_name, \"pre-receive\"))\n+\t\topt.jobs = 1;\n+\n+\tif (push_options) {\n+\t\tint i;\n+\t\tfor (i = 0; i < push_options->nr; i++)\n+\t\t\tstrvec_pushf(&opt.env, \"GIT_PUSH_OPTION_%d=%s\", i,\n+\t\t\t\t     push_options->items[i].string);\n+\t\tstrvec_pushf(&opt.env, \"GIT_PUSH_OPTION_COUNT=%d\", push_options->nr);\n+\t} else\n+\t\tstrvec_push(&opt.env, \"GIT_PUSH_OPTION_COUNT\");\n+\n+\tif (tmp_objdir)\n+\t\tstrvec_pushv(&opt.env, tmp_objdir_env(tmp_objdir));\n+\n+\tprepare_push_cert_sha1(&opt);\n+\n+\t/* set up sideband printer */\n+\tif (use_sideband)\n+\t\topt.consume_sideband = hook_output_to_sideband;\n+\n+\t/* set up stdin callback */\n+\tctx.cmd = commands;\n+\tctx.skip_broken = skip_broken;\n+\topt.feed_pipe = feed_receive_hook_cb;\n+\topt.feed_pipe_ctx = &ctx;\n+\n+\trc = run_hooks(hook_name, &opt);\n+\trun_hooks_opt_clear(&opt);\n+\treturn rc;\n+}\n+\n static int run_update_hook(struct command *cmd)\n {\n \tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"412823","messageId":"20201222000435.1529768-13-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201222000435.1529768-1-emilyshaffer@google.com","subject":"[PATCH v3 12/17] reference-transaction: look for hooks in config","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-22T00:04:30Z","receivedAt":"2020-12-22T00:06:16Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"By using the hook.h library, reference-transaction hooks can be\nspecified in the config instead.\n\nThe expected output of the test is not fully updated to reflect the\nabsolute path of the hook called because the 'update' hook has not yet\nbeen converted to use hook.h.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt       |  2 ++\n refs.c                           | 38 +++++++++-----------------------\n t/t1416-ref-transaction-hooks.sh |  8 +++----\n 3 files changed, 17 insertions(+), 31 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 8f5524055b..3a35500132 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -521,6 +521,8 @@ The exit status of the hook is ignored for any state except for the\n cause the transaction to be aborted. The hook will not be called with\n \"aborted\" state in that case.\n \n+Hooks run during 'reference-transaction' will be run in parallel by default.\n+\n push-to-checkout\n ~~~~~~~~~~~~~~~~\n \ndiff --git a/refs.c b/refs.c\nindex 392f0bbf68..b025dc4140 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -18,6 +18,7 @@\n #include \"strvec.h\"\n #include \"repository.h\"\n #include \"sigchain.h\"\n+#include \"hook.h\"\n \n /*\n  * List of all available backends\n@@ -1957,47 +1958,30 @@ int ref_update_reject_duplicates(struct string_list *refnames,\n static int run_transaction_hook(struct ref_transaction *transaction,\n \t\t\t\tconst char *state)\n {\n-\tstruct child_process proc = CHILD_PROCESS_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n-\tconst char *hook;\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;\n \tint ret = 0, i;\n+\tchar o[GIT_MAX_HEXSZ + 1], n[GIT_MAX_HEXSZ + 1];\n \n-\thook = find_hook(\"reference-transaction\");\n-\tif (!hook)\n+\tif (!hook_exists(\"reference-transaction\", configured_hookdir_opt()))\n \t\treturn ret;\n \n-\tstrvec_pushl(&proc.args, hook, state, NULL);\n-\tproc.in = -1;\n-\tproc.stdout_to_stderr = 1;\n-\tproc.trace2_hook_name = \"reference-transaction\";\n-\n-\tret = start_command(&proc);\n-\tif (ret)\n-\t\treturn ret;\n-\n-\tsigchain_push(SIGPIPE, SIG_IGN);\n+\tstrvec_push(&opt.args, state);\n \n \tfor (i = 0; i < transaction->nr; i++) {\n \t\tstruct ref_update *update = transaction->updates[i];\n+\t\toid_to_hex_r(o, &update->old_oid);\n+\t\toid_to_hex_r(n, &update->new_oid);\n \n \t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addf(&buf, \"%s %s %s\\n\",\n-\t\t\t    oid_to_hex(&update->old_oid),\n-\t\t\t    oid_to_hex(&update->new_oid),\n-\t\t\t    update->refname);\n-\n-\t\tif (write_in_full(proc.in, buf.buf, buf.len) < 0) {\n-\t\t\tif (errno != EPIPE)\n-\t\t\t\tret = -1;\n-\t\t\tbreak;\n-\t\t}\n+\t\tstrbuf_addf(&buf, \"%s %s %s\", o, n, update->refname);\n+\t\tstring_list_append(&opt.str_stdin, buf.buf);\n \t}\n \n-\tclose(proc.in);\n-\tsigchain_pop(SIGPIPE);\n+\tret = run_hooks(\"reference-transaction\", &opt);\n+\trun_hooks_opt_clear(&opt);\n \tstrbuf_release(&buf);\n \n-\tret |= finish_command(&proc);\n \treturn ret;\n }\n \ndiff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh\nindex f6e741c6c0..17f11f5cb0 100755\n--- a/t/t1416-ref-transaction-hooks.sh\n+++ b/t/t1416-ref-transaction-hooks.sh\n@@ -122,11 +122,11 @@ test_expect_success 'interleaving hook calls succeed' '\n \n \tcat >expect <<-EOF &&\n \t\thooks/update refs/tags/PRE $ZERO_OID $PRE_OID\n-\t\thooks/reference-transaction prepared\n-\t\thooks/reference-transaction committed\n+\t\t$(pwd)/target-repo.git/hooks/reference-transaction prepared\n+\t\t$(pwd)/target-repo.git/hooks/reference-transaction committed\n \t\thooks/update refs/tags/POST $ZERO_OID $POST_OID\n-\t\thooks/reference-transaction prepared\n-\t\thooks/reference-transaction committed\n+\t\t$(pwd)/target-repo.git/hooks/reference-transaction prepared\n+\t\t$(pwd)/target-repo.git/hooks/reference-transaction committed\n \tEOF\n \n \tgit push ./target-repo.git PRE POST &&\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"412825","messageId":"20201222000435.1529768-18-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201222000435.1529768-1-emilyshaffer@google.com","subject":"[PATCH v3 17/17] run-command: stop thinking about hooks","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-22T00:04:35Z","receivedAt":"2020-12-22T00:06:38Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"hook.h has replaced all run-command.h hook-related functionality.\nrun-command.h:run_hooks_le/ve and find_hook are no longer used anywhere\nin the codebase. So, let's delete the dead code - or, in the one case\nwhere it's still needed, move it to an internal function in hook.c.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n hook.c        | 39 ++++++++++++++++++++++++++++--\n run-command.c | 66 ---------------------------------------------------\n run-command.h | 24 -------------------\n 3 files changed, 37 insertions(+), 92 deletions(-)\n\ndiff --git a/hook.c b/hook.c\nindex dc241f7ec5..0ac984983e 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -196,6 +196,41 @@ static int should_include_hookdir(const char *path, enum hookdir_opt cfg)\n \t}\n }\n \n+static const char *find_legacy_hook(const char *name)\n+{\n+\tstatic struct strbuf path = STRBUF_INIT;\n+\n+\tstrbuf_reset(&path);\n+\tstrbuf_git_path(&path, \"hooks/%s\", name);\n+\tif (access(path.buf, X_OK) < 0) {\n+\t\tint err = errno;\n+\n+#ifdef STRIP_EXTENSION\n+\t\tstrbuf_addstr(&path, STRIP_EXTENSION);\n+\t\tif (access(path.buf, X_OK) >= 0)\n+\t\t\treturn path.buf;\n+\t\tif (errno == EACCES)\n+\t\t\terr = errno;\n+#endif\n+\n+\t\tif (err == EACCES && advice_ignored_hook) {\n+\t\t\tstatic struct string_list advise_given = STRING_LIST_INIT_DUP;\n+\n+\t\t\tif (!string_list_lookup(&advise_given, name)) {\n+\t\t\t\tstring_list_insert(&advise_given, name);\n+\t\t\t\tadvise(_(\"The '%s' hook was ignored because \"\n+\t\t\t\t\t \"it's not set as executable.\\n\"\n+\t\t\t\t\t \"You can disable this warning with \"\n+\t\t\t\t\t \"`git config advice.ignoredHook false`.\"),\n+\t\t\t\t       path.buf);\n+\t\t\t}\n+\t\t}\n+\t\treturn NULL;\n+\t}\n+\treturn path.buf;\n+}\n+\n+\n struct list_head* hook_list(const struct strbuf* hookname)\n {\n \tstruct strbuf hook_key = STRBUF_INIT;\n@@ -213,7 +248,7 @@ struct list_head* hook_list(const struct strbuf* hookname)\n \tgit_config(hook_config_lookup, (void*)&cb_data);\n \n \tif (have_git_dir())\n-\t\tlegacy_hook_path = find_hook(hookname->buf);\n+\t\tlegacy_hook_path = find_legacy_hook(hookname->buf);\n \n \t/* Unconditionally add legacy hook, but annotate it. */\n \tif (legacy_hook_path) {\n@@ -244,7 +279,7 @@ int hook_exists(const char *hookname, enum hookdir_opt should_run_hookdir)\n \tint could_run_hookdir = (should_run_hookdir == HOOKDIR_INTERACTIVE ||\n \t\t\t\tshould_run_hookdir == HOOKDIR_WARN ||\n \t\t\t\tshould_run_hookdir == HOOKDIR_YES)\n-\t\t\t\t&& !!find_hook(hookname);\n+\t\t\t\t&& !!find_legacy_hook(hookname);\n \n \tstrbuf_addf(&hook_key, \"hook.%s.command\", hookname);\n \ndiff --git a/run-command.c b/run-command.c\nindex 0dce6bec83..16656135dd 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -1310,72 +1310,6 @@ int async_with_fork(void)\n #endif\n }\n \n-const char *find_hook(const char *name)\n-{\n-\tstatic struct strbuf path = STRBUF_INIT;\n-\n-\tstrbuf_reset(&path);\n-\tstrbuf_git_path(&path, \"hooks/%s\", name);\n-\tif (access(path.buf, X_OK) < 0) {\n-\t\tint err = errno;\n-\n-#ifdef STRIP_EXTENSION\n-\t\tstrbuf_addstr(&path, STRIP_EXTENSION);\n-\t\tif (access(path.buf, X_OK) >= 0)\n-\t\t\treturn path.buf;\n-\t\tif (errno == EACCES)\n-\t\t\terr = errno;\n-#endif\n-\n-\t\tif (err == EACCES && advice_ignored_hook) {\n-\t\t\tstatic struct string_list advise_given = STRING_LIST_INIT_DUP;\n-\n-\t\t\tif (!string_list_lookup(&advise_given, name)) {\n-\t\t\t\tstring_list_insert(&advise_given, name);\n-\t\t\t\tadvise(_(\"The '%s' hook was ignored because \"\n-\t\t\t\t\t \"it's not set as executable.\\n\"\n-\t\t\t\t\t \"You can disable this warning with \"\n-\t\t\t\t\t \"`git config advice.ignoredHook false`.\"),\n-\t\t\t\t       path.buf);\n-\t\t\t}\n-\t\t}\n-\t\treturn NULL;\n-\t}\n-\treturn path.buf;\n-}\n-\n-int run_hook_ve(const char *const *env, const char *name, va_list args)\n-{\n-\tstruct child_process hook = CHILD_PROCESS_INIT;\n-\tconst char *p;\n-\n-\tp = find_hook(name);\n-\tif (!p)\n-\t\treturn 0;\n-\n-\tstrvec_push(&hook.args, p);\n-\twhile ((p = va_arg(args, const char *)))\n-\t\tstrvec_push(&hook.args, p);\n-\thook.env = env;\n-\thook.no_stdin = 1;\n-\thook.stdout_to_stderr = 1;\n-\thook.trace2_hook_name = name;\n-\n-\treturn run_command(&hook);\n-}\n-\n-int run_hook_le(const char *const *env, const char *name, ...)\n-{\n-\tva_list args;\n-\tint ret;\n-\n-\tva_start(args, name);\n-\tret = run_hook_ve(env, name, args);\n-\tva_end(args);\n-\n-\treturn ret;\n-}\n-\n struct io_pump {\n \t/* initialized by caller */\n \tint fd;\ndiff --git a/run-command.h b/run-command.h\nindex 2ad8271f56..e67bd22c5a 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -194,30 +194,6 @@ int finish_command_in_signal(struct child_process *);\n  */\n int run_command(struct child_process *);\n \n-/*\n- * Returns the path to the hook file, or NULL if the hook is missing\n- * or disabled. Note that this points to static storage that will be\n- * overwritten by further calls to find_hook and run_hook_*.\n- */\n-const char *find_hook(const char *name);\n-\n-/**\n- * Run a hook.\n- * The first argument is a pathname to an index file, or NULL\n- * if the hook uses the default index file or no index is needed.\n- * The second argument is the name of the hook.\n- * The further arguments correspond to the hook arguments.\n- * The last argument has to be NULL to terminate the arguments list.\n- * If the hook does not exist or is not executable, the return\n- * value will be zero.\n- * If it is executable, the hook will be executed and the exit\n- * status of the hook is returned.\n- * On execution, .stdout_to_stderr and .no_stdin will be set.\n- */\n-LAST_ARG_MUST_BE_NULL\n-int run_hook_le(const char *const *env, const char *name, ...);\n-int run_hook_ve(const char *const *env, const char *name, va_list args);\n-\n /*\n  * Trigger an auto-gc\n  */\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"412826","messageId":"20201222000435.1529768-16-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201222000435.1529768-1-emilyshaffer@google.com","subject":"[PATCH v3 15/17] post-update: use hook.h library","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-22T00:04:33Z","receivedAt":"2020-12-22T00:06:39Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"By using run_hooks() instead of run_hook_le(), 'post-update' hooks can\nbe specified in the config as well as the hookdir.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt |  2 ++\n builtin/receive-pack.c     | 26 +++++++-------------------\n 2 files changed, 9 insertions(+), 19 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 2c59c537f9..1412cd5846 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -498,6 +498,8 @@ Both standard output and standard error output are forwarded to\n `git send-pack` on the other end, so you can simply `echo` messages\n for the user.\n \n+Hooks run during 'post-update' will be run in parallel by default.\n+\n reference-transaction\n ~~~~~~~~~~~~~~~~~~~~~\n \ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 4a6ad27404..5c23d5ed86 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1686,33 +1686,21 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n static void run_update_post_hook(struct command *commands)\n {\n \tstruct command *cmd;\n-\tstruct child_process proc = CHILD_PROCESS_INIT;\n-\tconst char *hook;\n-\n-\thook = find_hook(\"post-update\");\n-\tif (!hook)\n-\t\treturn;\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;\n \n \tfor (cmd = commands; cmd; cmd = cmd->next) {\n \t\tif (cmd->error_string || cmd->did_not_exist)\n \t\t\tcontinue;\n-\t\tif (!proc.args.nr)\n-\t\t\tstrvec_push(&proc.args, hook);\n-\t\tstrvec_push(&proc.args, cmd->ref_name);\n+\t\tstrvec_push(&opt.args, cmd->ref_name);\n \t}\n-\tif (!proc.args.nr)\n+\tif (!opt.args.nr)\n \t\treturn;\n \n-\tproc.no_stdin = 1;\n-\tproc.stdout_to_stderr = 1;\n-\tproc.err = use_sideband ? -1 : 0;\n-\tproc.trace2_hook_name = \"post-update\";\n+\tif (use_sideband)\n+\t\topt.consume_sideband = hook_output_to_sideband;\n \n-\tif (!start_command(&proc)) {\n-\t\tif (use_sideband)\n-\t\t\tcopy_to_sideband(proc.err, -1, NULL);\n-\t\tfinish_command(&proc);\n-\t}\n+\trun_hooks(\"post-update\", &opt);\n+\trun_hooks_opt_clear(&opt);\n }\n \n static void check_aliased_update_internal(struct command *cmd,\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"412827","messageId":"20201222000435.1529768-12-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201222000435.1529768-1-emilyshaffer@google.com","subject":"[PATCH v3 11/17] transport: convert pre-push hook to use config","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-22T00:04:29Z","receivedAt":"2020-12-22T00:06:40Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"By using the hook.h:run_hooks API, pre-push hooks can be specified in\nthe config as well as in the hookdir.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n Documentation/githooks.txt |  2 ++\n transport.c                | 55 +++++++++-----------------------------\n 2 files changed, 14 insertions(+), 43 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex db290984f6..8f5524055b 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -271,6 +271,8 @@ If this hook exits with a non-zero status, `git push` will abort without\n pushing anything.  Information about why the push is rejected may be sent\n to the user by writing to standard error.\n \n+Hooks executed during 'pre-push' will run in parallel by default.\n+\n [[pre-receive]]\n pre-receive\n ~~~~~~~~~~~\ndiff --git a/transport.c b/transport.c\nindex 679a35e7c1..58fee65815 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -22,6 +22,7 @@\n #include \"protocol.h\"\n #include \"object-store.h\"\n #include \"color.h\"\n+#include \"hook.h\"\n \n static int transport_use_color = -1;\n static char transport_colors[][COLOR_MAXLEN] = {\n@@ -1172,31 +1173,13 @@ static void die_with_unpushed_submodules(struct string_list *needs_pushing)\n static int run_pre_push_hook(struct transport *transport,\n \t\t\t     struct ref *remote_refs)\n {\n-\tint ret = 0, x;\n+\tint ret = 0;\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT_ASYNC;\n+\tstruct strbuf tmp = STRBUF_INIT;\n \tstruct ref *r;\n-\tstruct child_process proc = CHILD_PROCESS_INIT;\n-\tstruct strbuf buf;\n-\tconst char *argv[4];\n-\n-\tif (!(argv[0] = find_hook(\"pre-push\")))\n-\t\treturn 0;\n-\n-\targv[1] = transport->remote->name;\n-\targv[2] = transport->url;\n-\targv[3] = NULL;\n-\n-\tproc.argv = argv;\n-\tproc.in = -1;\n-\tproc.trace2_hook_name = \"pre-push\";\n-\n-\tif (start_command(&proc)) {\n-\t\tfinish_command(&proc);\n-\t\treturn -1;\n-\t}\n \n-\tsigchain_push(SIGPIPE, SIG_IGN);\n-\n-\tstrbuf_init(&buf, 256);\n+\tstrvec_push(&opt.args, transport->remote->name);\n+\tstrvec_push(&opt.args, transport->url);\n \n \tfor (r = remote_refs; r; r = r->next) {\n \t\tif (!r->peer_ref) continue;\n@@ -1205,30 +1188,16 @@ static int run_pre_push_hook(struct transport *transport,\n \t\tif (r->status == REF_STATUS_REJECT_REMOTE_UPDATED) continue;\n \t\tif (r->status == REF_STATUS_UPTODATE) continue;\n \n-\t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addf( &buf, \"%s %s %s %s\\n\",\n+\t\tstrbuf_reset(&tmp);\n+\t\tstrbuf_addf(&tmp, \"%s %s %s %s\",\n \t\t\t r->peer_ref->name, oid_to_hex(&r->new_oid),\n \t\t\t r->name, oid_to_hex(&r->old_oid));\n-\n-\t\tif (write_in_full(proc.in, buf.buf, buf.len) < 0) {\n-\t\t\t/* We do not mind if a hook does not read all refs. */\n-\t\t\tif (errno != EPIPE)\n-\t\t\t\tret = -1;\n-\t\t\tbreak;\n-\t\t}\n+\t\tstring_list_append(&opt.str_stdin, tmp.buf);\n \t}\n \n-\tstrbuf_release(&buf);\n-\n-\tx = close(proc.in);\n-\tif (!ret)\n-\t\tret = x;\n-\n-\tsigchain_pop(SIGPIPE);\n-\n-\tx = finish_command(&proc);\n-\tif (!ret)\n-\t\tret = x;\n+\tret = run_hooks(\"pre-push\", &opt);\n+\trun_hooks_opt_clear(&opt);\n+\tstrbuf_release(&tmp);\n \n \treturn ret;\n }\n-- \n2.28.0.rc0.142.g3c755180ce-goog\n\n"},{"id":"413097","messageId":"X+pnK0336CPBaAEe@google.com","threadId":"54422","inReplyTo":"20201228224015.286845-1-emilyshaffer@google.com","subject":"Re: [PATCH v3 18/17] doc: make git-hook.txt point of truth","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-28T23:15:55Z","receivedAt":"2020-12-28T23:17:06Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Mon, Dec 28, 2020 at 02:40:15PM -0800, Emily Shaffer wrote:\n> \n> By showing the list of all hooks in 'git help hook' for users to refer\n> to, 'git help hook' becomes a one-stop shop for hook authorship. Since\n> some may still have muscle memory for 'git help githooks', though,\n> reference the 'git hook' commands and otherwise don't remove content.\n> \n> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>\n> ---\n> \n> Sorry for the wonky subject. It seemed unnecessary to send the entirety\n> of the topic again when there were no changes. (I was able to push this\n> patch to my fork without -f, so indeed the rest is unchanged.)\n> \n> I'd really prefer if 'git help githooks' opens the 'git-hook.txt' manpage,\n> but I couldn't figure out how to do that. This seemed like the next best thing\n> to me - users can still find information at the old manpage, but get a little\n> nudge towards the new manpage in case they didn't notice it.\n> \n> Extremely open to other notes about the direction of these docs; I'm not\n> confident in anything except that it'd be annoying to have 'git help\n> githooks' remain unchanged.\n\nEven now, this is missing the native-hooks.txt from the commit. Ugh.\nThis commit breaks the build; please do not add it to the topic. I will\ninclude a fixed version with the next reroll. Sorry.\n\nThat said, the intent is clear enough for me to still feel comfortable\ninviting review.\n\n - Emily\n"},{"id":"413100","messageId":"X+o5C78dAYWZhwhI@google.com","threadId":"54422","inReplyTo":"20201222000435.1529768-1-emilyshaffer@google.com","subject":"Re: [PATCH v3 00/17] use config-based hooks (config-based hooks part II)","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-28T19:59:07Z","receivedAt":"2020-12-28T23:23:03Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Mon, Dec 21, 2020 at 04:04:18PM -0800, Emily Shaffer wrote:\n> \n> Another thing I wanted to do in this series but ended up not having time\n> for before the holidays was to figure out a way to consolidate\n> Documentation/githooks.txt and Documentation/git-hook.txt. My personal\n> preference would be to remove githooks.txt's contents, move the \"Hooks\"\n> header from there into git-hook.txt, and have 'git help githooks'/'git\n> help hooks' redirect to git-hook.txt; I don't have a patch to share here\n> because I ran out of time before vacation :) What do others envision the\n> documentation looking like?\n\nI could use some tips on this, actually. I don't see anywhere that we\nmake one document redirect to another, or else I don't know what to look\nfor. If I replace the contents of `githooks.txt` with\n`include::git-hook.txt[]`, `generate-cmdlist.sh` chokes because there is\nno name to grep for in `githooks.txt`. Is there a documented example of\na manpage redirecting to a different manpage?\n\n - Emily\n"},{"id":"413109","messageId":"20201228224015.286845-1-emilyshaffer@google.com","threadId":"54422","inReplyTo":"20201222000435.1529768-1-emilyshaffer@google.com","subject":"[PATCH v3 18/17] doc: make git-hook.txt point of truth","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2020-12-28T22:40:15Z","receivedAt":"2020-12-28T23:23:12Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"By showing the list of all hooks in 'git help hook' for users to refer\nto, 'git help hook' becomes a one-stop shop for hook authorship. Since\nsome may still have muscle memory for 'git help githooks', though,\nreference the 'git hook' commands and otherwise don't remove content.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\n---\n\nSorry for the wonky subject. It seemed unnecessary to send the entirety\nof the topic again when there were no changes. (I was able to push this\npatch to my fork without -f, so indeed the rest is unchanged.)\n\nI'd really prefer if 'git help githooks' opens the 'git-hook.txt' manpage,\nbut I couldn't figure out how to do that. This seemed like the next best thing\nto me - users can still find information at the old manpage, but get a little\nnudge towards the new manpage in case they didn't notice it.\n\nExtremely open to other notes about the direction of these docs; I'm not\nconfident in anything except that it'd be annoying to have 'git help\ngithooks' remain unchanged.\n\n - Emily\n\n Documentation/git-hook.txt |   5 +\n Documentation/githooks.txt | 701 +------------------------------------\n 2 files changed, 13 insertions(+), 693 deletions(-)\n\ndiff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt\nindex 01cee4ad81..cb8e383ec9 100644\n--- a/Documentation/git-hook.txt\n+++ b/Documentation/git-hook.txt\n@@ -112,6 +112,11 @@ CONFIGURATION\n -------------\n include::config/hook.txt[]\n \n+HOOKS\n+-----\n+\n+include::native-hooks.txt[]\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex c450f7a27e..4d06369924 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -8,15 +8,20 @@ githooks - Hooks used by Git\n SYNOPSIS\n --------\n $GIT_DIR/hooks/* (or \\`git config core.hooksPath`/*)\n+[verse]\n+'git hook' list <hook-name>\n+'git hook' run <hook-name>\n \n \n DESCRIPTION\n -----------\n \n-Hooks are programs you can place in a hooks directory to trigger\n-actions at certain points in git's execution. Hooks that don't have\n+Hooks are programs you can place in a hooks directory or specify in the config\n+to trigger actions at certain points in Git's execution. Hooks that don't have\n the executable bit set are ignored.\n \n+For information about specifying hooks in the config, check linkgit:git-hook[1].\n+\n By default the hooks directory is `$GIT_DIR/hooks`, but that can be\n changed via the `core.hooksPath` configuration variable (see\n linkgit:git-config[1]).\n@@ -42,697 +47,7 @@ The currently supported hooks are described below.\n HOOKS\n -----\n \n-applypatch-msg\n-~~~~~~~~~~~~~~\n-\n-This hook is invoked by linkgit:git-am[1].  It takes a single\n-parameter, the name of the file that holds the proposed commit\n-log message.  Exiting with a non-zero status causes `git am` to abort\n-before applying the patch.\n-\n-The hook is allowed to edit the message file in place, and can\n-be used to normalize the message into some project standard\n-format. It can also be used to refuse the commit after inspecting\n-the message file.\n-\n-The default 'applypatch-msg' hook, when enabled, runs the\n-'commit-msg' hook, if the latter is enabled.\n-\n-Hooks run during 'applypatch-msg' will not be parallelized.\n-\n-pre-applypatch\n-~~~~~~~~~~~~~~\n-\n-This hook is invoked by linkgit:git-am[1].  It takes no parameter, and is\n-invoked after the patch is applied, but before a commit is made.\n-\n-If it exits with non-zero status, then the working tree will not be\n-committed after applying the patch.\n-\n-It can be used to inspect the current working tree and refuse to\n-make a commit if it does not pass certain test.\n-\n-The default 'pre-applypatch' hook, when enabled, runs the\n-'pre-commit' hook, if the latter is enabled.\n-\n-Hooks run during 'pre-applypatch' will be run in parallel by default.\n-\n-post-applypatch\n-~~~~~~~~~~~~~~~\n-\n-This hook is invoked by linkgit:git-am[1].  It takes no parameter,\n-and is invoked after the patch is applied and a commit is made.\n-\n-This hook is meant primarily for notification, and cannot affect\n-the outcome of `git am`.\n-\n-Hooks run during 'post-applypatch' will be run in parallel by default.\n-\n-pre-commit\n-~~~~~~~~~~\n-\n-This hook is invoked by linkgit:git-commit[1], and can be bypassed\n-with the `--no-verify` option.  It takes no parameters, and is\n-invoked before obtaining the proposed commit log message and\n-making a commit.  Exiting with a non-zero status from this script\n-causes the `git commit` command to abort before creating a commit.\n-\n-The default 'pre-commit' hook, when enabled, catches introduction\n-of lines with trailing whitespaces and aborts the commit when\n-such a line is found.\n-\n-All the `git commit` hooks are invoked with the environment\n-variable `GIT_EDITOR=:` if the command will not bring up an editor\n-to modify the commit message.\n-\n-The default 'pre-commit' hook, when enabled--and with the\n-`hooks.allownonascii` config option unset or set to false--prevents\n-the use of non-ASCII filenames.\n-\n-Hooks executed during 'pre-commit' will not be parallelized.\n-\n-pre-merge-commit\n-~~~~~~~~~~~~~~~~\n-\n-This hook is invoked by linkgit:git-merge[1], and can be bypassed\n-with the `--no-verify` option.  It takes no parameters, and is\n-invoked after the merge has been carried out successfully and before\n-obtaining the proposed commit log message to\n-make a commit.  Exiting with a non-zero status from this script\n-causes the `git merge` command to abort before creating a commit.\n-\n-The default 'pre-merge-commit' hook, when enabled, runs the\n-'pre-commit' hook, if the latter is enabled.\n-\n-This hook is invoked with the environment variable\n-`GIT_EDITOR=:` if the command will not bring up an editor\n-to modify the commit message.\n-\n-If the merge cannot be carried out automatically, the conflicts\n-need to be resolved and the result committed separately (see\n-linkgit:git-merge[1]). At that point, this hook will not be executed,\n-but the 'pre-commit' hook will, if it is enabled.\n-\n-Hooks executed during 'pre-merge-commit' will not be parallelized.\n-\n-prepare-commit-msg\n-~~~~~~~~~~~~~~~~~~\n-\n-This hook is invoked by linkgit:git-commit[1] right after preparing the\n-default log message, and before the editor is started.\n-\n-It takes one to three parameters.  The first is the name of the file\n-that contains the commit log message.  The second is the source of the commit\n-message, and can be: `message` (if a `-m` or `-F` option was\n-given); `template` (if a `-t` option was given or the\n-configuration option `commit.template` is set); `merge` (if the\n-commit is a merge or a `.git/MERGE_MSG` file exists); `squash`\n-(if a `.git/SQUASH_MSG` file exists); or `commit`, followed by\n-a commit SHA-1 (if a `-c`, `-C` or `--amend` option was given).\n-\n-If the exit status is non-zero, `git commit` will abort.\n-\n-The purpose of the hook is to edit the message file in place, and\n-it is not suppressed by the `--no-verify` option.  A non-zero exit\n-means a failure of the hook and aborts the commit.  It should not\n-be used as replacement for pre-commit hook.\n-\n-The sample `prepare-commit-msg` hook that comes with Git removes the\n-help message found in the commented portion of the commit template.\n-\n-Hooks executed during 'prepare-commit-msg' will not be parallelized.\n-\n-commit-msg\n-~~~~~~~~~~\n-\n-This hook is invoked by linkgit:git-commit[1] and linkgit:git-merge[1], and can be\n-bypassed with the `--no-verify` option.  It takes a single parameter,\n-the name of the file that holds the proposed commit log message.\n-Exiting with a non-zero status causes the command to abort.\n-\n-The hook is allowed to edit the message file in place, and can be used\n-to normalize the message into some project standard format. It\n-can also be used to refuse the commit after inspecting the message\n-file.\n-\n-The default 'commit-msg' hook, when enabled, detects duplicate\n-`Signed-off-by` trailers, and aborts the commit if one is found.\n-\n-Hooks executed during 'commit-msg' will not be parallelized.\n-\n-post-commit\n-~~~~~~~~~~~\n-\n-This hook is invoked by linkgit:git-commit[1]. It takes no parameters, and is\n-invoked after a commit is made.\n-\n-This hook is meant primarily for notification, and cannot affect\n-the outcome of `git commit`.\n-\n-Hooks executed during 'post-commit' will run in parallel by default.\n-\n-pre-rebase\n-~~~~~~~~~~\n-\n-This hook is called by linkgit:git-rebase[1] and can be used to prevent a\n-branch from getting rebased.  The hook may be called with one or\n-two parameters.  The first parameter is the upstream from which\n-the series was forked.  The second parameter is the branch being\n-rebased, and is not set when rebasing the current branch.\n-\n-Hooks executed during 'pre-rebase' will run in parallel by default.\n-\n-post-checkout\n-~~~~~~~~~~~~~\n-\n-This hook is invoked when a linkgit:git-checkout[1] or\n-linkgit:git-switch[1] is run after having updated the\n-worktree.  The hook is given three parameters: the ref of the previous HEAD,\n-the ref of the new HEAD (which may or may not have changed), and a flag\n-indicating whether the checkout was a branch checkout (changing branches,\n-flag=1) or a file checkout (retrieving a file from the index, flag=0).\n-This hook cannot affect the outcome of `git switch` or `git checkout`,\n-other than that the hook's exit status becomes the exit status of\n-these two commands.\n-\n-It is also run after linkgit:git-clone[1], unless the `--no-checkout` (`-n`) option is\n-used. The first parameter given to the hook is the null-ref, the second the\n-ref of the new HEAD and the flag is always 1. Likewise for `git worktree add`\n-unless `--no-checkout` is used.\n-\n-This hook can be used to perform repository validity checks, auto-display\n-differences from the previous HEAD if different, or set working dir metadata\n-properties.\n-\n-Hooks executed during 'post-checkout' will not be parallelized.\n-\n-post-merge\n-~~~~~~~~~~\n-\n-This hook is invoked by linkgit:git-merge[1], which happens when a `git pull`\n-is done on a local repository.  The hook takes a single parameter, a status\n-flag specifying whether or not the merge being done was a squash merge.\n-This hook cannot affect the outcome of `git merge` and is not executed,\n-if the merge failed due to conflicts.\n-\n-This hook can be used in conjunction with a corresponding pre-commit hook to\n-save and restore any form of metadata associated with the working tree\n-(e.g.: permissions/ownership, ACLS, etc).  See contrib/hooks/setgitperms.perl\n-for an example of how to do this.\n-\n-Hooks executed during 'post-merge' will run in parallel by default.\n-\n-pre-push\n-~~~~~~~~\n-\n-This hook is called by linkgit:git-push[1] and can be used to prevent\n-a push from taking place.  The hook is called with two parameters\n-which provide the name and location of the destination remote, if a\n-named remote is not being used both values will be the same.\n-\n-Information about what is to be pushed is provided on the hook's standard\n-input with lines of the form:\n-\n-  <local ref> SP <local sha1> SP <remote ref> SP <remote sha1> LF\n-\n-For instance, if the command +git push origin master:foreign+ were run the\n-hook would receive a line like the following:\n-\n-  refs/heads/master 67890 refs/heads/foreign 12345\n-\n-although the full, 40-character SHA-1s would be supplied.  If the foreign ref\n-does not yet exist the `<remote SHA-1>` will be 40 `0`.  If a ref is to be\n-deleted, the `<local ref>` will be supplied as `(delete)` and the `<local\n-SHA-1>` will be 40 `0`.  If the local commit was specified by something other\n-than a name which could be expanded (such as `HEAD~`, or a SHA-1) it will be\n-supplied as it was originally given.\n-\n-If this hook exits with a non-zero status, `git push` will abort without\n-pushing anything.  Information about why the push is rejected may be sent\n-to the user by writing to standard error.\n-\n-Hooks executed during 'pre-push' will run in parallel by default.\n-\n-[[pre-receive]]\n-pre-receive\n-~~~~~~~~~~~\n-\n-This hook is invoked by linkgit:git-receive-pack[1] when it reacts to\n-`git push` and updates reference(s) in its repository.\n-Just before starting to update refs on the remote repository, the\n-pre-receive hook is invoked.  Its exit status determines the success\n-or failure of the update.\n-\n-This hook executes once for the receive operation. It takes no\n-arguments, but for each ref to be updated it receives on standard\n-input a line of the format:\n-\n-  <old-value> SP <new-value> SP <ref-name> LF\n-\n-where `<old-value>` is the old object name stored in the ref,\n-`<new-value>` is the new object name to be stored in the ref and\n-`<ref-name>` is the full name of the ref.\n-When creating a new ref, `<old-value>` is 40 `0`.\n-\n-If the hook exits with non-zero status, none of the refs will be\n-updated. If the hook exits with zero, updating of individual refs can\n-still be prevented by the <<update,'update'>> hook.\n-\n-Both standard output and standard error output are forwarded to\n-`git send-pack` on the other end, so you can simply `echo` messages\n-for the user.\n-\n-The number of push options given on the command line of\n-`git push --push-option=...` can be read from the environment\n-variable `GIT_PUSH_OPTION_COUNT`, and the options themselves are\n-found in `GIT_PUSH_OPTION_0`, `GIT_PUSH_OPTION_1`,...\n-If it is negotiated to not use the push options phase, the\n-environment variables will not be set. If the client selects\n-to use push options, but doesn't transmit any, the count variable\n-will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.\n-\n-See the section on \"Quarantine Environment\" in\n-linkgit:git-receive-pack[1] for some caveats.\n-\n-Hooks executed during 'pre-receive' will not be parallelized.\n-\n-[[update]]\n-update\n-~~~~~~\n-\n-This hook is invoked by linkgit:git-receive-pack[1] when it reacts to\n-`git push` and updates reference(s) in its repository.\n-Just before updating the ref on the remote repository, the update hook\n-is invoked.  Its exit status determines the success or failure of\n-the ref update.\n-\n-The hook executes once for each ref to be updated, and takes\n-three parameters:\n-\n- - the name of the ref being updated,\n- - the old object name stored in the ref,\n- - and the new object name to be stored in the ref.\n-\n-A zero exit from the update hook allows the ref to be updated.\n-Exiting with a non-zero status prevents `git receive-pack`\n-from updating that ref.\n-\n-This hook can be used to prevent 'forced' update on certain refs by\n-making sure that the object name is a commit object that is a\n-descendant of the commit object named by the old object name.\n-That is, to enforce a \"fast-forward only\" policy.\n-\n-It could also be used to log the old..new status.  However, it\n-does not know the entire set of branches, so it would end up\n-firing one e-mail per ref when used naively, though.  The\n-<<post-receive,'post-receive'>> hook is more suited to that.\n-\n-In an environment that restricts the users' access only to git\n-commands over the wire, this hook can be used to implement access\n-control without relying on filesystem ownership and group\n-membership. See linkgit:git-shell[1] for how you might use the login\n-shell to restrict the user's access to only git commands.\n-\n-Both standard output and standard error output are forwarded to\n-`git send-pack` on the other end, so you can simply `echo` messages\n-for the user.\n-\n-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-Hooks executed during 'update' are run in parallel by default.\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-It is forbidden to specify more than one hook for 'proc-receive'. If a\n-globally-configured 'proc-receive' must be overridden, use\n-'hookcmd.<global-hook>.skip = true' to ignore it.\n-\n-[[post-receive]]\n-post-receive\n-~~~~~~~~~~~~\n-\n-This hook is invoked by linkgit:git-receive-pack[1] when it reacts to\n-`git push` and updates reference(s) in its repository.\n-It executes on the remote repository once after all the refs have\n-been updated.\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'>>\n-hook does on its standard input.\n-\n-This hook does not affect the outcome of `git receive-pack`, as it\n-is called after the real work is done.\n-\n-This supersedes the <<post-update,'post-update'>> hook in that it gets\n-both old and new values of all the refs in addition to their\n-names.\n-\n-Both standard output and standard error output are forwarded to\n-`git send-pack` on the other end, so you can simply `echo` messages\n-for the user.\n-\n-The default 'post-receive' hook is empty, but there is\n-a sample script `post-receive-email` provided in the `contrib/hooks`\n-directory in Git distribution, which implements sending commit\n-emails.\n-\n-The number of push options given on the command line of\n-`git push --push-option=...` can be read from the environment\n-variable `GIT_PUSH_OPTION_COUNT`, and the options themselves are\n-found in `GIT_PUSH_OPTION_0`, `GIT_PUSH_OPTION_1`,...\n-If it is negotiated to not use the push options phase, the\n-environment variables will not be set. If the client selects\n-to use push options, but doesn't transmit any, the count variable\n-will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.\n-\n-Hooks executed during 'post-receive' are run in parallel by default.\n-\n-[[post-update]]\n-post-update\n-~~~~~~~~~~~\n-\n-This hook is invoked by linkgit:git-receive-pack[1] when it reacts to\n-`git push` and updates reference(s) in its repository.\n-It executes on the remote repository once after all the refs have\n-been updated.\n-\n-It takes a variable number of parameters, each of which is the\n-name of ref that was actually updated.\n-\n-This hook is meant primarily for notification, and cannot affect\n-the outcome of `git receive-pack`.\n-\n-The 'post-update' hook can tell what are the heads that were pushed,\n-but it does not know what their original and updated values are,\n-so it is a poor place to do log old..new. The\n-<<post-receive,'post-receive'>> hook does get both original and\n-updated values of the refs. You might consider it instead if you need\n-them.\n-\n-When enabled, the default 'post-update' hook runs\n-`git update-server-info` to keep the information used by dumb\n-transports (e.g., HTTP) up to date.  If you are publishing\n-a Git repository that is accessible via HTTP, you should\n-probably enable this hook.\n-\n-Both standard output and standard error output are forwarded to\n-`git send-pack` on the other end, so you can simply `echo` messages\n-for the user.\n-\n-Hooks run during 'post-update' will be run in parallel by default.\n-\n-reference-transaction\n-~~~~~~~~~~~~~~~~~~~~~\n-\n-This hook is invoked by any Git command that performs reference\n-updates. It executes whenever a reference transaction is prepared,\n-committed or aborted and may thus get called multiple times.\n-\n-The hook takes exactly one argument, which is the current state the\n-given reference transaction is in:\n-\n-    - \"prepared\": All reference updates have been queued to the\n-      transaction and references were locked on disk.\n-\n-    - \"committed\": The reference transaction was committed and all\n-      references now have their respective new value.\n-\n-    - \"aborted\": The reference transaction was aborted, no changes\n-      were performed and the locks have been released.\n-\n-For each reference update that was added to the transaction, the hook\n-receives on standard input a line of the format:\n-\n-  <old-value> SP <new-value> SP <ref-name> LF\n-\n-The exit status of the hook is ignored for any state except for the\n-\"prepared\" state. In the \"prepared\" state, a non-zero exit status will\n-cause the transaction to be aborted. The hook will not be called with\n-\"aborted\" state in that case.\n-\n-Hooks run during 'reference-transaction' will be run in parallel by default.\n-\n-push-to-checkout\n-~~~~~~~~~~~~~~~~\n-\n-This hook is invoked by linkgit:git-receive-pack[1] when it reacts to\n-`git push` and updates reference(s) in its repository, and when\n-the push tries to update the branch that is currently checked out\n-and the `receive.denyCurrentBranch` configuration variable is set to\n-`updateInstead`.  Such a push by default is refused if the working\n-tree and the index of the remote repository has any difference from\n-the currently checked out commit; when both the working tree and the\n-index match the current commit, they are updated to match the newly\n-pushed tip of the branch.  This hook is to be used to override the\n-default behaviour.\n-\n-The hook receives the commit with which the tip of the current\n-branch is going to be updated.  It can exit with a non-zero status\n-to refuse the push (when it does so, it must not modify the index or\n-the working tree).  Or it can make any necessary changes to the\n-working tree and to the index to bring them to the desired state\n-when the tip of the current branch is updated to the new commit, and\n-exit with a zero status.\n-\n-For example, the hook can simply run `git read-tree -u -m HEAD \"$1\"`\n-in order to emulate `git fetch` that is run in the reverse direction\n-with `git push`, as the two-tree form of `git read-tree -u -m` is\n-essentially the same as `git switch` or `git checkout`\n-that switches branches while\n-keeping the local changes in the working tree that do not interfere\n-with the difference between the branches.\n-\n-Hooks executed during 'push-to-checkout' will not be parallelized.\n-\n-pre-auto-gc\n-~~~~~~~~~~~\n-\n-This hook is invoked by `git gc --auto` (see linkgit:git-gc[1]). It\n-takes no parameter, and exiting with non-zero status from this script\n-causes the `git gc --auto` to abort.\n-\n-Hooks run during 'pre-auto-gc' will be run in parallel by default.\n-\n-post-rewrite\n-~~~~~~~~~~~~\n-\n-This hook is invoked by commands that rewrite commits\n-(linkgit:git-commit[1] when called with `--amend` and\n-linkgit:git-rebase[1]; however, full-history (re)writing tools like\n-linkgit:git-fast-import[1] or\n-https://github.com/newren/git-filter-repo[git-filter-repo] typically\n-do not call it!).  Its first argument denotes the command it was\n-invoked by: currently one of `amend` or `rebase`.  Further\n-command-dependent arguments may be passed in the future.\n-\n-The hook receives a list of the rewritten commits on stdin, in the\n-format\n-\n-  <old-sha1> SP <new-sha1> [ SP <extra-info> ] LF\n-\n-The 'extra-info' is again command-dependent.  If it is empty, the\n-preceding SP is also omitted.  Currently, no commands pass any\n-'extra-info'.\n-\n-The hook always runs after the automatic note copying (see\n-\"notes.rewrite.<command>\" in linkgit:git-config[1]) has happened, and\n-thus has access to these notes.\n-\n-Hooks run during 'post-rewrite' will be run in parallel by default.\n-\n-The following command-specific comments apply:\n-\n-rebase::\n-\tFor the 'squash' and 'fixup' operation, all commits that were\n-\tsquashed are listed as being rewritten to the squashed commit.\n-\tThis means that there will be several lines sharing the same\n-\t'new-sha1'.\n-+\n-The commits are guaranteed to be listed in the order that they were\n-processed by rebase.\n-\n-sendemail-validate\n-~~~~~~~~~~~~~~~~~~\n-\n-This hook is invoked by linkgit:git-send-email[1].  It takes a single parameter,\n-the name of the file that holds the e-mail to be sent.  Exiting with a\n-non-zero status causes `git send-email` to abort before sending any\n-e-mails.\n-\n-fsmonitor-watchman\n-~~~~~~~~~~~~~~~~~~\n-\n-This hook is invoked when the configuration option `core.fsmonitor` is\n-set to `.git/hooks/fsmonitor-watchman` or `.git/hooks/fsmonitor-watchmanv2`\n-depending on the version of the hook to use.\n-\n-Version 1 takes two arguments, a version (1) and the time in elapsed\n-nanoseconds since midnight, January 1, 1970.\n-\n-Version 2 takes two arguments, a version (2) and a token that is used\n-for identifying changes since the token. For watchman this would be\n-a clock id. This version must output to stdout the new token followed\n-by a NUL before the list of files.\n-\n-The hook should output to stdout the list of all files in the working\n-directory that may have changed since the requested time.  The logic\n-should be inclusive so that it does not miss any potential changes.\n-The paths should be relative to the root of the working directory\n-and be separated by a single NUL.\n-\n-It is OK to include files which have not actually changed.  All changes\n-including newly-created and deleted files should be included. When\n-files are renamed, both the old and the new name should be included.\n-\n-Git will limit what files it checks for changes as well as which\n-directories are checked for untracked files based on the path names\n-given.\n-\n-An optimized way to tell git \"all files have changed\" is to return\n-the filename `/`.\n-\n-The exit status determines whether git will use the data from the\n-hook to limit its search.  On error, it will fall back to verifying\n-all files and folders.\n-\n-p4-changelist\n-~~~~~~~~~~~~~\n-\n-This hook is invoked by `git-p4 submit`.\n-\n-The `p4-changelist` hook is executed after the changelist\n-message has been edited by the user. It can be bypassed with the\n-`--no-verify` option. It takes a single parameter, the name\n-of the file that holds the proposed changelist text. Exiting\n-with a non-zero status causes the command to abort.\n-\n-The hook is allowed to edit the changelist file and can be used\n-to normalize the text into some project standard format. It can\n-also be used to refuse the Submit after inspect the message file.\n-\n-Run `git-p4 submit --help` for details.\n-\n-p4-prepare-changelist\n-~~~~~~~~~~~~~~~~~~~~~\n-\n-This hook is invoked by `git-p4 submit`.\n-\n-The `p4-prepare-changelist` hook is executed right after preparing\n-the default changelist message and before the editor is started.\n-It takes one parameter, the name of the file that contains the\n-changelist text. Exiting with a non-zero status from the script\n-will abort the process.\n-\n-The purpose of the hook is to edit the message file in place,\n-and it is not supressed by the `--no-verify` option. This hook\n-is called even if `--prepare-p4-only` is set.\n-\n-Run `git-p4 submit --help` for details.\n-\n-p4-post-changelist\n-~~~~~~~~~~~~~~~~~~\n-\n-This hook is invoked by `git-p4 submit`.\n-\n-The `p4-post-changelist` hook is invoked after the submit has\n-successfully occurred in P4. It takes no parameters and is meant\n-primarily for notification and cannot affect the outcome of the\n-git p4 submit action.\n-\n-Run `git-p4 submit --help` for details.\n-\n-p4-pre-submit\n-~~~~~~~~~~~~~\n-\n-This hook is invoked by `git-p4 submit`. It takes no parameters and nothing\n-from standard input. Exiting with non-zero status from this script prevent\n-`git-p4 submit` from launching. It can be bypassed with the `--no-verify`\n-command line option. Run `git-p4 submit --help` for details.\n-\n-\n-\n-post-index-change\n-~~~~~~~~~~~~~~~~~\n-\n-This hook is invoked when the index is written in read-cache.c\n-do_write_locked_index.\n-\n-The first parameter passed to the hook is the indicator for the\n-working directory being updated.  \"1\" meaning working directory\n-was updated or \"0\" when the working directory was not updated.\n-\n-The second parameter passed to the hook is the indicator for whether\n-or not the index was updated and the skip-worktree bit could have\n-changed.  \"1\" meaning skip-worktree bits could have been updated\n-and \"0\" meaning they were not.\n-\n-Only one parameter should be set to \"1\" when the hook runs.  The hook\n-running passing \"1\", \"1\" should not be possible.\n-\n-Hooks run during 'post-index-change' will be run in parallel by default.\n+include::native-hooks.txt[]\n \n GIT\n ---\n-- \n2.29.2.490.gc7ae633391\n\n"},{"id":"415830","messageId":"xmqqsg6fjvfs.fsf@gitster.c.googlers.com","threadId":"54422","inReplyTo":"20201222000435.1529768-3-emilyshaffer@google.com","subject":"Re: [PATCH v3 02/17] am: convert applypatch hooks to use config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-01T22:05:11Z","receivedAt":"2021-02-01T22:07:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Emily Shaffer <emilyshaffer@google.com> writes:\n\n> Teach pre-applypatch, post-applypatch, and applypatch-msg to use the\n> hook.h library instead of the run-command.h library. This enables use of\n> hooks specified in the config, in addition to those in the hookdir.\n> These three hooks are called only by builtin/am.c.\n\nI was interested in the applypatch-msg the most; it gets fed the\n\"final-commit\" file as its input and expected to modify it in place,\nso if you allow two or more to work in series, you do not have to\nanything extra (like swapping the output from the previous step to\nthe input for the next step), which makes this a relatively easy\nconversion.\n"},{"id":"415832","messageId":"xmqqlfc7jv9p.fsf@gitster.c.googlers.com","threadId":"54422","inReplyTo":"20201222000435.1529768-2-emilyshaffer@google.com","subject":"Re: [PATCH v3 01/17] commit: use config-based hooks","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-01T22:08:50Z","receivedAt":"2021-02-01T22:09:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Emily Shaffer <emilyshaffer@google.com> writes:\n\n> Because all but 'post-commit' hooks are expected to make some state\n> change, force all but 'post-commit' hook to run in series.\n\nOK.  And a sequence of hooks that run in series are expected to use\nthe output from the previous one as its input?\n\n> 'post-commit'\n> \"is meant primarily for notification, and cannot affect the outcome of\n> `git commit`,\" so it is fine to run in parallel.\n\nMakes sense.\n\n"},{"id":"415840","messageId":"xmqqwnvrie8k.fsf@gitster.c.googlers.com","threadId":"54422","inReplyTo":"20201222000435.1529768-2-emilyshaffer@google.com","subject":"Re: [PATCH v3 01/17] commit: use config-based hooks","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-01T23:02:03Z","receivedAt":"2021-02-01T23:02:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Emily Shaffer <emilyshaffer@google.com> writes:\n\n> @@ -175,6 +183,8 @@ invoked after a commit is made.\n>  This hook is meant primarily for notification, and cannot affect\n>  the outcome of `git commit`.\n>  \n> +Hooks executed during 'post-commit' will run in parallel by default.\n\nThe \"by default\" makes it sound as if there is a way to force\n'post-commit' alone serially, but other than hook.jobs that can be\nused to uniformly make everything run serially, I didn't find a way\nto do so.\n\nThe comment applies to all the \"by default\" in this latter half of\nthe series.\n\nThanks.\n"},{"id":"417367","messageId":"YC7q9okiZVEp8Cwx@google.com","threadId":"54422","inReplyTo":"20201222000435.1529768-1-emilyshaffer@google.com","subject":"Re: [PATCH v3 00/17] use config-based hooks (config-based hooks part II)","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-02-18T22:32:22Z","receivedAt":"2021-02-18T22:33:21Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2020.12.21 16:04, Emily Shaffer wrote:\n> Since v2:\n>  - Renamed 'master' to 'main' in new t5411 (proc-receive) test.\n>  - Removed some accidentally included debug strings.\n>  - Fixed a nasty bug in the reference-transaction hook conversion where calling\n>    'oid_to_hex()' would invalidate references to the output of earlier\n>    'oid_to_hex()' runs farther up the callstack. Instead, the hook callsite now\n>    uses 'oid_to_hex_r()'.\n> \n> Another thing I wanted to do in this series but ended up not having time\n> for before the holidays was to figure out a way to consolidate\n> Documentation/githooks.txt and Documentation/git-hook.txt. My personal\n> preference would be to remove githooks.txt's contents, move the \"Hooks\"\n> header from there into git-hook.txt, and have 'git help githooks'/'git\n> help hooks' redirect to git-hook.txt; I don't have a patch to share here\n> because I ran out of time before vacation :) What do others envision the\n> documentation looking like?\n> \n> Thanks!\n>  - Emily\n> \n> CI run: https://github.com/nasamuffin/git/actions/runs/436905873\n> \n> Emily Shaffer (17):\n>   commit: use config-based hooks\n>   am: convert applypatch hooks to use config\n>   merge: use config-based hooks for post-merge hook\n>   gc: use hook library for pre-auto-gc hook\n>   rebase: teach pre-rebase to use hook.h\n>   read-cache: convert post-index-change hook to use config\n>   receive-pack: convert push-to-checkout hook to hook.h\n>   git-p4: use 'git hook' to run hooks\n>   hooks: convert 'post-checkout' hook to hook library\n>   hook: convert 'post-rewrite' hook to config\n>   transport: convert pre-push hook to use config\n>   reference-transaction: look for hooks in config\n>   receive-pack: convert 'update' hook to hook.h\n>   proc-receive: acquire hook list from hook.h\n>   post-update: use hook.h library\n>   receive-pack: convert receive hooks to hook.h\n>   run-command: stop thinking about hooks\n> \n>  Documentation/githooks.txt                    |  45 +++\n>  builtin/am.c                                  |  30 +-\n>  builtin/checkout.c                            |  16 +-\n>  builtin/clone.c                               |   7 +-\n>  builtin/commit.c                              |  11 +-\n>  builtin/gc.c                                  |   4 +-\n>  builtin/merge.c                               |  14 +-\n>  builtin/rebase.c                              |   7 +-\n>  builtin/receive-pack.c                        | 326 ++++++++++--------\n>  builtin/worktree.c                            |  30 +-\n>  commit.c                                      |  20 +-\n>  commit.h                                      |   3 +-\n>  git-p4.py                                     |  67 +---\n>  hook.c                                        |  39 ++-\n>  read-cache.c                                  |  12 +-\n>  refs.c                                        |  38 +-\n>  reset.c                                       |  15 +-\n>  run-command.c                                 |  66 ----\n>  run-command.h                                 |  24 --\n>  sequencer.c                                   |  83 ++---\n>  t/t1416-ref-transaction-hooks.sh              |  12 +-\n>  t/t5411/test-0015-too-many-hooks-error.sh     |  47 +++\n>  ...3-pre-commit-and-pre-merge-commit-hooks.sh |  17 +-\n>  transport.c                                   |  55 +--\n>  24 files changed, 493 insertions(+), 495 deletions(-)\n>  create mode 100644 t/t5411/test-0015-too-many-hooks-error.sh\n> \n> -- \n> 2.28.0.rc0.142.g3c755180ce-goog\n> \n\nMy only complaints on the previous version of this series have now been\nresolved, so LGTM.\n\nReviewed-by: Josh Steadmon <steadmon@google.com>\n"},{"id":"418725","messageId":"YEkgWDthF7sObtAt@google.com","threadId":"54422","inReplyTo":"xmqqwnvrie8k.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v3 01/17] commit: use config-based hooks","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2021-03-10T19:39:04Z","receivedAt":"2021-03-10T19:39:48Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Mon, Feb 01, 2021 at 03:02:03PM -0800, Junio C Hamano wrote:\n> \n> Emily Shaffer <emilyshaffer@google.com> writes:\n> \n> > @@ -175,6 +183,8 @@ invoked after a commit is made.\n> >  This hook is meant primarily for notification, and cannot affect\n> >  the outcome of `git commit`.\n> >  \n> > +Hooks executed during 'post-commit' will run in parallel by default.\n> \n> The \"by default\" makes it sound as if there is a way to force\n> 'post-commit' alone serially, but other than hook.jobs that can be\n> used to uniformly make everything run serially, I didn't find a way\n> to do so.\n\nYeah, hook.jobs is the way to make it run serially, and as you say, it\ncan't be applied everywhere. I'll try to change the language.\n\n> \n> The comment applies to all the \"by default\" in this latter half of\n> the series.\n> \n> Thanks.\n"},{"id":"418733","messageId":"YEkjSSuHXk3XI85W@google.com","threadId":"54422","inReplyTo":"xmqqlfc7jv9p.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v3 01/17] commit: use config-based hooks","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2021-03-10T19:51:37Z","receivedAt":"2021-03-10T19:52:47Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Mon, Feb 01, 2021 at 02:08:50PM -0800, Junio C Hamano wrote:\n> \n> Emily Shaffer <emilyshaffer@google.com> writes:\n> \n> > Because all but 'post-commit' hooks are expected to make some state\n> > change, force all but 'post-commit' hook to run in series.\n> \n> OK.  And a sequence of hooks that run in series are expected to use\n> the output from the previous one as its input?\n\nKind of, but phrasing it that way is a little odd (to me). The commit\nhooks take as input the path to a tempfile which holds the proposed\ncommit message, and they modify the contents of that file. They don't\nmake output via stdout or receive the commit message via stdin. Most\nhooks work this way, I think with the sole exception of 'proc-receive',\nwhich is implemented weirdly (I said more about it when you asked about\nit in your part I review, here:\nhttps://lore.kernel.org/git/YEkgWDthF7sObtAt@google.com).\n\nI'm not sure how to phrase it better. If I were writing a hook I imagine\nthe combination of taking a filename as an argument and the note about\nnot running these hooks in parallel would be pretty self-evident. Hmmm.\nMaybe instead of the copy/pasted warning about \"will not be\nparallelized\" I could write:\n\n  Hooks executed during prepare-commit-msg will not be parallelized,\n  because the file provided in the first argument is expected to be edited\n  by each hook.\n\n> \n> > 'post-commit'\n> > \"is meant primarily for notification, and cannot affect the outcome of\n> > `git commit`,\" so it is fine to run in parallel.\n> \n> Makes sense.\n> \n"},{"id":"418747","messageId":"xmqqh7liiqkb.fsf@gitster.g","threadId":"54422","inReplyTo":"YEkjSSuHXk3XI85W@google.com","subject":"Re: [PATCH v3 01/17] commit: use config-based hooks","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-03-10T22:36:52Z","receivedAt":"2021-03-10T22:37:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Emily Shaffer <emilyshaffer@google.com> writes:\n\n> parallelized\" I could write:\n>\n>   Hooks executed during prepare-commit-msg will not be parallelized,\n>   because the file provided in the first argument is expected to be edited\n>   by each hook.\n\nYeah.  It's been quite a while since I wrote the message you are\nresponding to, and it took me a while to get the context back in my\nhead, but the above looks reasonable to me.  We could tweak it a bit\nto say \"the file ... serves as an input to be modified in place by\neach hook\" to make it even clearer, but I think yours is clear\nenough.\n\nThanks.\n"}]}