{"thread":{"id":"56717","subject":"[PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion","startedAt":"2021-10-15T09:43:48Z","lastAt":"2022-01-07T23:08:55Z","messageCount":115,"participants":["Ævar Arnfjörð Bjarmason","Emily Shaffer","Eric Sunshine","Junio C Hamano","Glen Choo"],"isPatch":true,"patchVersion":2,"patchTotal":13},"messages":[{"id":"438819","messageId":"cover-v2-00.13-00000000000-20211015T093918Z-avarab@gmail.com","threadId":"56717","inReplyTo":null,"subject":"[PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-15T09:43:28Z","receivedAt":"2021-10-15T09:43:48Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Part 2 of the greater configurable hook saga, starting by converting\nsome existing simple hooks to the new hook.[ch] library and \"git hook\nrun\" utility.\n\nSee the v1 [1] CL for more context. I've dropped the git-p4 folks from\nthe CC list, your feedback on git-p4 would still be very appreciated,\nbut I don't think I need to re-spam you for every re-roll of this.\n\nThis v2:\n\n * Should address all outstanding comments on v1, which were mainly of\n   the form that some boilerplate here didn't reflect the actual state\n   of the code.\n\n   I've updated the docs, code etc. to 1=1 map to the existing\n   behavior. I said I'd punt on e.g. the one-member \"struct hook\" in\n   reply to René Scharfe, but when I tried it and rebased the rest on\n   it it actually made the progression more understandable, so I've\n   done that here.\n\n   I didn't convert some things, which are noted in the updated commit\n   message on v1, e.g. we still use the parallel run-command.c API\n   with jobs=1, and have a function called run_hooks() that only ever\n   runs one hook.\n\n1. https://lore.kernel.org/git/cover-00.13-00000000000-20211012T131934Z-avarab@gmail.com/\n\nEmily Shaffer (12):\n  hook: add 'run' subcommand\n  gc: use hook library for pre-auto-gc hook\n  rebase: convert pre-rebase to use hook.h\n  am: convert applypatch to use hook.h\n  hooks: convert 'post-checkout' hook to hook library\n  merge: convert post-merge to use hook.h\n  send-email: use 'git hook run' for 'sendemail-validate'\n  git-p4: use 'git hook' to run hooks\n  commit: convert {pre-commit,prepare-commit-msg} hook to hook.h\n  read-cache: convert post-index-change to use hook.h\n  receive-pack: convert push-to-checkout hook to hook.h\n  run-command: remove old run_hook_{le,ve}() hook API\n\nÆvar Arnfjörð Bjarmason (1):\n  git hook run: add an --ignore-missing flag\n\n .gitignore                 |   1 +\n Documentation/git-hook.txt |  45 +++++++++++++\n Documentation/githooks.txt |   4 ++\n Makefile                   |   1 +\n builtin.h                  |   1 +\n builtin/am.c               |   8 ++-\n builtin/checkout.c         |  14 ++--\n builtin/clone.c            |   7 +-\n builtin/gc.c               |   3 +-\n builtin/hook.c             |  90 +++++++++++++++++++++++++\n builtin/merge.c            |   4 +-\n builtin/rebase.c           |   8 ++-\n builtin/receive-pack.c     |   7 +-\n builtin/worktree.c         |  28 ++++----\n command-list.txt           |   1 +\n commit.c                   |  15 +++--\n git-p4.py                  |  72 ++------------------\n git-send-email.perl        |  22 +++---\n git.c                      |   1 +\n hook.c                     | 121 +++++++++++++++++++++++++++++++++\n hook.h                     |  56 ++++++++++++++++\n read-cache.c               |  11 ++-\n reset.c                    |  14 ++--\n run-command.c              |  32 ---------\n run-command.h              |  17 -----\n t/t1800-hook.sh            | 134 +++++++++++++++++++++++++++++++++++++\n t/t9001-send-email.sh      |   4 +-\n 27 files changed, 551 insertions(+), 170 deletions(-)\n create mode 100644 Documentation/git-hook.txt\n create mode 100644 builtin/hook.c\n create mode 100755 t/t1800-hook.sh\n\nRange-diff against v1:\n 1:  a39c0748d3f !  1:  ba64faf0580 hook: add 'run' subcommand\n    @@ Commit message\n         pattern here mirrors that of\n         builtin/{commit-graph,multi-pack-index}.c.\n     \n    +    Some of the implementation here, such as a function being named\n    +    run_hooks() when it's tasked with running one hook, to using the\n    +    run_processes_parallel_tr2() API to run with jobs=1 is somewhere\n    +    between a bit odd and and an overkill for the current features of this\n    +    \"hook run\" command and the hook.[ch] API.\n    +\n    +    This code will eventually be able to run multiple hooks declared in\n    +    config in parallel, by starting out with these names and APIs we\n    +    reduce the later churn of renaming functions, switching from the\n    +    run_command() to run_processes_parallel_tr2() API etc.\n    +\n         Signed-off-by: Emily Shaffer <emilyshaffer@google.com>\n         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n    @@ Documentation/git-hook.txt (new)\n     +\n     +NAME\n     +----\n    -+git-hook - run git hooks\n    ++git-hook - Run git hooks\n     +\n     +SYNOPSIS\n     +--------\n    @@ Documentation/git-hook.txt (new)\n     +DESCRIPTION\n     +-----------\n     +\n    -+This command is an interface to git hooks (see linkgit:githooks[5]).\n    -+Currently it only provides a convenience wrapper for running hooks for\n    -+use by git itself. In the future it might gain other functionality.\n    ++A command interface to running git hooks (see linkgit:githooks[5]),\n    ++for use by other scripted git commands.\n     +\n     +SUBCOMMANDS\n     +-----------\n     +\n     +run::\n     +\tRun the `<hook-name>` hook. See linkgit:githooks[5] for\n    -+\tthe hook names we support.\n    ++\tsupported hook names.\n     ++\n    -+Any positional arguments to the hook should be passed after an\n    -+optional `--` (or `--end-of-options`, see linkgit:gitcli[7]). The\n    -+arguments (if any) differ by hook name, see linkgit:githooks[5] for\n    -+what those are.\n    ++\n    ++Any positional arguments to the hook should be passed after a\n    ++mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See\n    ++linkgit:githooks[5] for arguments hooks might expect (if any).\n     +\n     +SEE ALSO\n     +--------\n    @@ builtin/hook.c (new)\n     +\t/* Need to take into account core.hooksPath */\n     +\tgit_config(git_default_config, NULL);\n     +\n    -+\t/*\n    -+\t * We are not using a plain run_hooks() because we'd like to\n    -+\t * detect missing hooks. Let's find it ourselves and call\n    -+\t * run_hooks() instead.\n    -+\t */\n     +\thook_name = argv[0];\n     +\thook_path = find_hook(hook_name);\n     +\tif (!hook_path) {\n    @@ command-list.txt: git-grep                                mainporcelain\n      git-gui                                 mainporcelain\n      git-hash-object                         plumbingmanipulators\n      git-help                                ancillaryinterrogators          complete\n    -+git-hook                                mainporcelain\n    ++git-hook                                purehelpers\n      git-http-backend                        synchingrepositories\n      git-http-fetch                          synchelpers\n      git-http-push                           synchelpers\n    @@ hook.c: int hook_exists(const char *name)\n     +\t\t\t  void **pp_task_cb)\n     +{\n     +\tstruct hook_cb_data *hook_cb = pp_cb;\n    -+\tstruct hook *run_me = hook_cb->run_me;\n    ++\tconst char *hook_path = hook_cb->hook_path;\n     +\n    -+\tif (!run_me)\n    ++\tif (!hook_path)\n     +\t\treturn 0;\n     +\n     +\tcp->no_stdin = 1;\n    @@ hook.c: int hook_exists(const char *name)\n     +\tcp->stdout_to_stderr = 1;\n     +\tcp->trace2_hook_name = hook_cb->hook_name;\n     +\n    -+\t/* add command */\n    -+\tstrvec_push(&cp->args, run_me->hook_path);\n    -+\n    -+\t/*\n    -+\t * add passed-in argv, without expanding - let the user get back\n    -+\t * exactly what they put in\n    -+\t */\n    ++\tstrvec_push(&cp->args, hook_path);\n     +\tstrvec_pushv(&cp->args, hook_cb->options->args.v);\n     +\n     +\t/* Provide context for errors if necessary */\n    -+\t*pp_task_cb = run_me;\n    ++\t*pp_task_cb = (char *)hook_path;\n     +\n     +\t/*\n     +\t * This pick_next_hook() will be called again, we're only\n     +\t * running one hook, so indicate that no more work will be\n     +\t * done.\n     +\t */\n    -+\thook_cb->run_me = NULL;\n    ++\thook_cb->hook_path = NULL;\n     +\n     +\treturn 1;\n     +}\n    @@ hook.c: int hook_exists(const char *name)\n     +\t\t\t\tvoid *pp_task_cp)\n     +{\n     +\tstruct hook_cb_data *hook_cb = pp_cb;\n    -+\tstruct hook *attempted = pp_task_cp;\n    ++\tconst char *hook_path = pp_task_cp;\n     +\n     +\thook_cb->rc |= 1;\n     +\n     +\tstrbuf_addf(out, _(\"Couldn't start hook '%s'\\n\"),\n    -+\t\t    attempted->hook_path);\n    ++\t\t    hook_path);\n     +\n     +\treturn 1;\n     +}\n    @@ hook.c: int hook_exists(const char *name)\n     +int run_hooks(const char *hook_name, const char *hook_path,\n     +\t      struct run_hooks_opt *options)\n     +{\n    -+\tstruct hook my_hook = {\n    -+\t\t.hook_path = hook_path,\n    -+\t};\n     +\tstruct hook_cb_data cb_data = {\n     +\t\t.rc = 0,\n     +\t\t.hook_name = hook_name,\n    ++\t\t.hook_path = hook_path,\n     +\t\t.options = options,\n     +\t};\n     +\tint jobs = 1;\n    @@ hook.c: int hook_exists(const char *name)\n     +\tif (!options)\n     +\t\tBUG(\"a struct run_hooks_opt must be provided to run_hooks\");\n     +\n    -+\tcb_data.run_me = &my_hook;\n    -+\n     +\trun_processes_parallel_tr2(jobs,\n     +\t\t\t\t   pick_next_hook,\n     +\t\t\t\t   notify_start_failure,\n    @@ hook.h\n      #define HOOK_H\n     +#include \"strvec.h\"\n     +\n    -+struct hook {\n    -+\t/* The path to the hook */\n    -+\tconst char *hook_path;\n    -+};\n    -+\n     +struct run_hooks_opt\n     +{\n     +\t/* Environment vars to be set for each hook */\n    @@ hook.h\n     +\t/* rc reflects the cumulative failure state */\n     +\tint rc;\n     +\tconst char *hook_name;\n    -+\tstruct hook *run_me;\n    ++\tconst char *hook_path;\n     +\tstruct run_hooks_opt *options;\n     +};\n      \n    @@ hook.h: const char *find_hook(const char *name);\n      int hook_exists(const char *hookname);\n      \n     +/**\n    -+ * Clear data from an initialized \"struct run_hooks-opt\".\n    ++ * Clear data from an initialized \"struct run_hooks_opt\".\n     + */\n     +void run_hooks_opt_clear(struct run_hooks_opt *o);\n     +\n 2:  dbac4204f7b =  2:  e3dc0aed81b gc: use hook library for pre-auto-gc hook\n 3:  ff306debcb8 =  3:  6227a1e644d rebase: convert pre-rebase to use hook.h\n 4:  b1d529ca485 =  4:  0e34eb54054 am: convert applypatch to use hook.h\n 5:  15d71fc210b !  5:  a4df96c1719 hooks: convert 'post-checkout' hook to hook library\n    @@ hook.c: static int pick_next_hook(struct child_process *cp,\n      \tcp->trace2_hook_name = hook_cb->hook_name;\n     +\tcp->dir = hook_cb->options->dir;\n      \n    - \t/* add command */\n    - \tstrvec_push(&cp->args, run_me->hook_path);\n    + \tstrvec_push(&cp->args, hook_path);\n    + \tstrvec_pushv(&cp->args, hook_cb->options->args.v);\n     @@ hook.c: static int notify_hook_finished(int result,\n      int run_hooks(const char *hook_name, const char *hook_path,\n      \t      struct run_hooks_opt *options)\n      {\n     +\tstruct strbuf abs_path = STRBUF_INIT;\n    - \tstruct hook my_hook = {\n    - \t\t.hook_path = hook_path,\n    - \t};\n    + \tstruct hook_cb_data cb_data = {\n    + \t\t.rc = 0,\n    + \t\t.hook_name = hook_name,\n     @@ hook.c: int run_hooks(const char *hook_name, const char *hook_path,\n      \tif (!options)\n      \t\tBUG(\"a struct run_hooks_opt must be provided to run_hooks\");\n      \n     +\tif (options->absolute_path) {\n     +\t\tstrbuf_add_absolute_path(&abs_path, hook_path);\n    -+\t\tmy_hook.hook_path = abs_path.buf;\n    ++\t\thook_path = abs_path.buf;\n     +\t}\n    - \tcb_data.run_me = &my_hook;\n    - \n    ++\n      \trun_processes_parallel_tr2(jobs,\n    + \t\t\t\t   pick_next_hook,\n    + \t\t\t\t   notify_start_failure,\n     @@ hook.c: int run_hooks(const char *hook_name, const char *hook_path,\n      \t\t\t\t   \"hook\",\n      \t\t\t\t   hook_name);\n 6:  08f27f0d6be =  6:  327f916f8c3 merge: convert post-merge to use hook.h\n 7:  107c14d740f !  7:  328767015b1 git hook run: add an --ignore-missing flag\n    @@ Commit message\n         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n      ## Documentation/git-hook.txt ##\n    -@@ Documentation/git-hook.txt: git-hook - run git hooks\n    +@@ Documentation/git-hook.txt: git-hook - Run git hooks\n      SYNOPSIS\n      --------\n      [verse]\n    @@ Documentation/git-hook.txt: git-hook - run git hooks\n      \n      DESCRIPTION\n      -----------\n    -@@ Documentation/git-hook.txt: optional `--` (or `--end-of-options`, see linkgit:gitcli[7]). The\n    - arguments (if any) differ by hook name, see linkgit:githooks[5] for\n    - what those are.\n    +@@ Documentation/git-hook.txt: Any positional arguments to the hook should be passed after a\n    + mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See\n    + linkgit:githooks[5] for arguments hooks might expect (if any).\n      \n     +OPTIONS\n     +-------\n    @@ builtin/hook.c: static int run(int argc, const char **argv, const char *prefix)\n      \t};\n      \tint ret;\n     @@ builtin/hook.c: static int run(int argc, const char **argv, const char *prefix)\n    - \t/*\n    - \t * We are not using a plain run_hooks() because we'd like to\n    - \t * detect missing hooks. Let's find it ourselves and call\n    --\t * run_hooks() instead.\n    -+\t * run_hooks() instead...\n    - \t */\n    + \tgit_config(git_default_config, NULL);\n    + \n      \thook_name = argv[0];\n     +\tif (ignore_missing)\n    -+\t\t/* ... act like a plain run_hooks() under --ignore-missing */\n     +\t\treturn run_hooks_oneshot(hook_name, &opt);\n      \thook_path = find_hook(hook_name);\n      \tif (!hook_path) {\n 8:  1d30e2dbbe0 =  8:  6c4ebd68d56 send-email: use 'git hook run' for 'sendemail-validate'\n 9:  69cc447a1e1 =  9:  b1f52733e3c git-p4: use 'git hook' to run hooks\n10:  1c22b2992cf = 10:  dc31d98acdf commit: convert {pre-commit,prepare-commit-msg} hook to hook.h\n11:  e762fce32af = 11:  58b7689e4af read-cache: convert post-index-change to use hook.h\n12:  d63b91196ae = 12:  ae1e2a82147 receive-pack: convert push-to-checkout hook to hook.h\n13:  fe8996dda3e = 13:  289d5a2d849 run-command: remove old run_hook_{le,ve}() hook API\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"438820","messageId":"patch-v2-01.13-ba64faf0580-20211015T093918Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v2-00.13-00000000000-20211015T093918Z-avarab@gmail.com","subject":"[PATCH v2 01/13] hook: add 'run' subcommand","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-15T09:43:29Z","receivedAt":"2021-10-15T09:43:53Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nIn order to enable hooks to be run as an external process, by a\nstandalone Git command, or by tools which wrap Git, provide an external\nmeans to run all configured hook commands for a given hook event.\n\nMost of our hooks require more complex functionality than this, but\nlet's start with the bare minimum required to support our simplest\nhooks.\n\nIn terms of implementation the usage_with_options() and \"goto usage\"\npattern here mirrors that of\nbuiltin/{commit-graph,multi-pack-index}.c.\n\nSome of the implementation here, such as a function being named\nrun_hooks() when it's tasked with running one hook, to using the\nrun_processes_parallel_tr2() API to run with jobs=1 is somewhere\nbetween a bit odd and and an overkill for the current features of this\n\"hook run\" command and the hook.[ch] API.\n\nThis code will eventually be able to run multiple hooks declared in\nconfig in parallel, by starting out with these names and APIs we\nreduce the later churn of renaming functions, switching from the\nrun_command() to run_processes_parallel_tr2() API etc.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n .gitignore                 |   1 +\n Documentation/git-hook.txt |  37 +++++++++++\n Documentation/githooks.txt |   4 ++\n Makefile                   |   1 +\n builtin.h                  |   1 +\n builtin/hook.c             |  85 ++++++++++++++++++++++++\n command-list.txt           |   1 +\n git.c                      |   1 +\n hook.c                     |  91 ++++++++++++++++++++++++++\n hook.h                     |  34 ++++++++++\n t/t1800-hook.sh            | 129 +++++++++++++++++++++++++++++++++++++\n 11 files changed, 385 insertions(+)\n create mode 100644 Documentation/git-hook.txt\n create mode 100644 builtin/hook.c\n create mode 100755 t/t1800-hook.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex 80c6bf6f3a8..005be1415ed 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -77,6 +77,7 @@\n /git-grep\n /git-hash-object\n /git-help\n+/git-hook\n /git-http-backend\n /git-http-fetch\n /git-http-push\ndiff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt\nnew file mode 100644\nindex 00000000000..e39b1b5d069\n--- /dev/null\n+++ b/Documentation/git-hook.txt\n@@ -0,0 +1,37 @@\n+git-hook(1)\n+===========\n+\n+NAME\n+----\n+git-hook - Run git hooks\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git hook' run <hook-name> [-- <hook-args>]\n+\n+DESCRIPTION\n+-----------\n+\n+A command interface to running git hooks (see linkgit:githooks[5]),\n+for use by other scripted git commands.\n+\n+SUBCOMMANDS\n+-----------\n+\n+run::\n+\tRun the `<hook-name>` hook. See linkgit:githooks[5] for\n+\tsupported hook names.\n++\n+\n+Any positional arguments to the hook should be passed after a\n+mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See\n+linkgit:githooks[5] for arguments hooks might expect (if any).\n+\n+SEE ALSO\n+--------\n+linkgit:githooks[5]\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex b51959ff941..a16e62bc8c8 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -698,6 +698,10 @@ 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+SEE ALSO\n+--------\n+linkgit:git-hook[1]\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex 060a8c46475..c95997f8d21 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1108,6 +1108,7 @@ BUILTIN_OBJS += builtin/get-tar-commit-id.o\n BUILTIN_OBJS += builtin/grep.o\n BUILTIN_OBJS += builtin/hash-object.o\n BUILTIN_OBJS += builtin/help.o\n+BUILTIN_OBJS += builtin/hook.o\n BUILTIN_OBJS += builtin/index-pack.o\n BUILTIN_OBJS += builtin/init-db.o\n BUILTIN_OBJS += builtin/interpret-trailers.o\ndiff --git a/builtin.h b/builtin.h\nindex 8a58743ed63..83379f3832c 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -164,6 +164,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);\n int cmd_grep(int argc, const char **argv, const char *prefix);\n int cmd_hash_object(int argc, const char **argv, const char *prefix);\n int cmd_help(int argc, const char **argv, const char *prefix);\n+int cmd_hook(int argc, const char **argv, const char *prefix);\n int cmd_index_pack(int argc, const char **argv, const char *prefix);\n int cmd_init_db(int argc, const char **argv, const char *prefix);\n int cmd_interpret_trailers(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/hook.c b/builtin/hook.c\nnew file mode 100644\nindex 00000000000..41dd15550cf\n--- /dev/null\n+++ b/builtin/hook.c\n@@ -0,0 +1,85 @@\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"config.h\"\n+#include \"hook.h\"\n+#include \"parse-options.h\"\n+#include \"strbuf.h\"\n+#include \"strvec.h\"\n+\n+#define BUILTIN_HOOK_RUN_USAGE \\\n+\tN_(\"git hook run <hook-name> [-- <hook-args>]\")\n+\n+static const char * const builtin_hook_usage[] = {\n+\tBUILTIN_HOOK_RUN_USAGE,\n+\tNULL\n+};\n+\n+static const char * const builtin_hook_run_usage[] = {\n+\tBUILTIN_HOOK_RUN_USAGE,\n+\tNULL\n+};\n+\n+static int run(int argc, const char **argv, const char *prefix)\n+{\n+\tint i;\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n+\tconst char *hook_name;\n+\tconst char *hook_path;\n+\tstruct option run_options[] = {\n+\t\tOPT_END(),\n+\t};\n+\tint ret;\n+\n+\targc = parse_options(argc, argv, prefix, run_options,\n+\t\t\t     builtin_hook_run_usage,\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n+\n+\tif (!argc)\n+\t\tgoto usage;\n+\n+\t/*\n+\t * Having a -- for \"run\" when providing <hook-args> is\n+\t * mandatory.\n+\t */\n+\tif (argc > 1 && strcmp(argv[1], \"--\") &&\n+\t    strcmp(argv[1], \"--end-of-options\"))\n+\t\tgoto usage;\n+\n+\t/* Add our arguments, start after -- */\n+\tfor (i = 2 ; i < argc; i++)\n+\t\tstrvec_push(&opt.args, argv[i]);\n+\n+\t/* Need to take into account core.hooksPath */\n+\tgit_config(git_default_config, NULL);\n+\n+\thook_name = argv[0];\n+\thook_path = find_hook(hook_name);\n+\tif (!hook_path) {\n+\t\terror(\"cannot find a hook named %s\", hook_name);\n+\t\treturn 1;\n+\t}\n+\n+\tret = run_hooks(hook_name, hook_path, &opt);\n+\trun_hooks_opt_clear(&opt);\n+\treturn ret;\n+usage:\n+\tusage_with_options(builtin_hook_run_usage, run_options);\n+}\n+\n+int cmd_hook(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct option builtin_hook_options[] = {\n+\t\tOPT_END(),\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, builtin_hook_options,\n+\t\t\t     builtin_hook_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n+\tif (!argc)\n+\t\tgoto usage;\n+\n+\tif (!strcmp(argv[0], \"run\"))\n+\t\treturn run(argc, argv, prefix);\n+\n+usage:\n+\tusage_with_options(builtin_hook_usage, builtin_hook_options);\n+}\ndiff --git a/command-list.txt b/command-list.txt\nindex a289f09ed6f..17c3958802e 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -103,6 +103,7 @@ git-grep                                mainporcelain           info\n git-gui                                 mainporcelain\n git-hash-object                         plumbingmanipulators\n git-help                                ancillaryinterrogators          complete\n+git-hook                                purehelpers\n git-http-backend                        synchingrepositories\n git-http-fetch                          synchelpers\n git-http-push                           synchelpers\ndiff --git a/git.c b/git.c\nindex 60c2784be45..e5891e02021 100644\n--- a/git.c\n+++ b/git.c\n@@ -538,6 +538,7 @@ static struct cmd_struct commands[] = {\n \t{ \"grep\", cmd_grep, RUN_SETUP_GENTLY },\n \t{ \"hash-object\", cmd_hash_object },\n \t{ \"help\", cmd_help },\n+\t{ \"hook\", cmd_hook, RUN_SETUP },\n \t{ \"index-pack\", cmd_index_pack, RUN_SETUP_GENTLY | NO_PARSEOPT },\n \t{ \"init\", cmd_init_db },\n \t{ \"init-db\", cmd_init_db },\ndiff --git a/hook.c b/hook.c\nindex 55e1145a4b7..20a1a4fec33 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -1,6 +1,7 @@\n #include \"cache.h\"\n #include \"hook.h\"\n #include \"run-command.h\"\n+#include \"config.h\"\n \n const char *find_hook(const char *name)\n {\n@@ -40,3 +41,93 @@ int hook_exists(const char *name)\n {\n \treturn !!find_hook(name);\n }\n+\n+void run_hooks_opt_clear(struct run_hooks_opt *o)\n+{\n+\tstrvec_clear(&o->env);\n+\tstrvec_clear(&o->args);\n+}\n+\n+static int pick_next_hook(struct child_process *cp,\n+\t\t\t  struct strbuf *out,\n+\t\t\t  void *pp_cb,\n+\t\t\t  void **pp_task_cb)\n+{\n+\tstruct hook_cb_data *hook_cb = pp_cb;\n+\tconst char *hook_path = hook_cb->hook_path;\n+\n+\tif (!hook_path)\n+\t\treturn 0;\n+\n+\tcp->no_stdin = 1;\n+\tcp->env = hook_cb->options->env.v;\n+\tcp->stdout_to_stderr = 1;\n+\tcp->trace2_hook_name = hook_cb->hook_name;\n+\n+\tstrvec_push(&cp->args, hook_path);\n+\tstrvec_pushv(&cp->args, hook_cb->options->args.v);\n+\n+\t/* Provide context for errors if necessary */\n+\t*pp_task_cb = (char *)hook_path;\n+\n+\t/*\n+\t * This pick_next_hook() will be called again, we're only\n+\t * running one hook, so indicate that no more work will be\n+\t * done.\n+\t */\n+\thook_cb->hook_path = NULL;\n+\n+\treturn 1;\n+}\n+\n+static int notify_start_failure(struct strbuf *out,\n+\t\t\t\tvoid *pp_cb,\n+\t\t\t\tvoid *pp_task_cp)\n+{\n+\tstruct hook_cb_data *hook_cb = pp_cb;\n+\tconst char *hook_path = pp_task_cp;\n+\n+\thook_cb->rc |= 1;\n+\n+\tstrbuf_addf(out, _(\"Couldn't start hook '%s'\\n\"),\n+\t\t    hook_path);\n+\n+\treturn 1;\n+}\n+\n+static int notify_hook_finished(int result,\n+\t\t\t\tstruct strbuf *out,\n+\t\t\t\tvoid *pp_cb,\n+\t\t\t\tvoid *pp_task_cb)\n+{\n+\tstruct hook_cb_data *hook_cb = pp_cb;\n+\n+\thook_cb->rc |= result;\n+\n+\treturn 0;\n+}\n+\n+int run_hooks(const char *hook_name, const char *hook_path,\n+\t      struct run_hooks_opt *options)\n+{\n+\tstruct hook_cb_data cb_data = {\n+\t\t.rc = 0,\n+\t\t.hook_name = hook_name,\n+\t\t.hook_path = hook_path,\n+\t\t.options = options,\n+\t};\n+\tint jobs = 1;\n+\n+\tif (!options)\n+\t\tBUG(\"a struct run_hooks_opt must be provided to run_hooks\");\n+\n+\trun_processes_parallel_tr2(jobs,\n+\t\t\t\t   pick_next_hook,\n+\t\t\t\t   notify_start_failure,\n+\t\t\t\t   notify_hook_finished,\n+\t\t\t\t   &cb_data,\n+\t\t\t\t   \"hook\",\n+\t\t\t\t   hook_name);\n+\n+\treturn cb_data.rc;\n+}\ndiff --git a/hook.h b/hook.h\nindex 6aa36fc7ff9..a9317c3f95e 100644\n--- a/hook.h\n+++ b/hook.h\n@@ -1,5 +1,28 @@\n #ifndef HOOK_H\n #define HOOK_H\n+#include \"strvec.h\"\n+\n+struct run_hooks_opt\n+{\n+\t/* Environment vars to be set for each hook */\n+\tstruct strvec env;\n+\n+\t/* Args to be passed to each hook */\n+\tstruct strvec args;\n+};\n+\n+#define RUN_HOOKS_OPT_INIT { \\\n+\t.env = STRVEC_INIT, \\\n+\t.args = STRVEC_INIT, \\\n+}\n+\n+struct hook_cb_data {\n+\t/* rc reflects the cumulative failure state */\n+\tint rc;\n+\tconst char *hook_name;\n+\tconst char *hook_path;\n+\tstruct run_hooks_opt *options;\n+};\n \n /*\n  * Returns the path to the hook file, or NULL if the hook is missing\n@@ -13,4 +36,15 @@ const char *find_hook(const char *name);\n  */\n int hook_exists(const char *hookname);\n \n+/**\n+ * Clear data from an initialized \"struct run_hooks_opt\".\n+ */\n+void run_hooks_opt_clear(struct run_hooks_opt *o);\n+\n+/**\n+ * Takes an already resolved hook found via find_hook() and runs\n+ * it. Does not call run_hooks_opt_clear() for you.\n+ */\n+int run_hooks(const char *hookname, const char *hook_path,\n+\t      struct run_hooks_opt *options);\n #endif\ndiff --git a/t/t1800-hook.sh b/t/t1800-hook.sh\nnew file mode 100755\nindex 00000000000..3aea1b105f0\n--- /dev/null\n+++ b/t/t1800-hook.sh\n@@ -0,0 +1,129 @@\n+#!/bin/sh\n+\n+test_description='git-hook command'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n+. ./test-lib.sh\n+\n+test_expect_success 'git hook usage' '\n+\ttest_expect_code 129 git hook &&\n+\ttest_expect_code 129 git hook run &&\n+\ttest_expect_code 129 git hook run -h &&\n+\ttest_expect_code 129 git hook run --unknown 2>err &&\n+\tgrep \"unknown option\" err\n+'\n+\n+test_expect_success 'git hook run: nonexistent hook' '\n+\tcat >stderr.expect <<-\\EOF &&\n+\terror: cannot find a hook named test-hook\n+\tEOF\n+\ttest_expect_code 1 git hook run test-hook 2>stderr.actual &&\n+\ttest_cmp stderr.expect stderr.actual\n+'\n+\n+test_expect_success 'git hook run: basic' '\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\techo Test hook\n+\tEOF\n+\n+\tcat >expect <<-\\EOF &&\n+\tTest hook\n+\tEOF\n+\tgit hook run test-hook 2>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git hook run: stdout and stderr both write to our stderr' '\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\techo >&1 Will end up on stderr\n+\techo >&2 Will end up on stderr\n+\tEOF\n+\n+\tcat >stderr.expect <<-\\EOF &&\n+\tWill end up on stderr\n+\tWill end up on stderr\n+\tEOF\n+\tgit hook run test-hook >stdout.actual 2>stderr.actual &&\n+\ttest_cmp stderr.expect stderr.actual &&\n+\ttest_must_be_empty stdout.actual\n+'\n+\n+test_expect_success 'git hook run: exit codes are passed along' '\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\texit 1\n+\tEOF\n+\n+\ttest_expect_code 1 git hook run test-hook &&\n+\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\texit 2\n+\tEOF\n+\n+\ttest_expect_code 2 git hook run test-hook &&\n+\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\texit 128\n+\tEOF\n+\n+\ttest_expect_code 128 git hook run test-hook &&\n+\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\texit 129\n+\tEOF\n+\n+\ttest_expect_code 129 git hook run test-hook\n+'\n+\n+test_expect_success 'git hook run arg u ments without -- is not allowed' '\n+\ttest_expect_code 129 git hook run test-hook arg u ments\n+'\n+\n+test_expect_success 'git hook run -- pass arguments' '\n+\twrite_script .git/hooks/test-hook <<-\\EOF &&\n+\techo $1\n+\techo $2\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\targ\n+\tu ments\n+\tEOF\n+\n+\tgit hook run test-hook -- arg \"u ments\" 2>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git hook run -- out-of-repo runs excluded' '\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\techo Test hook\n+\tEOF\n+\n+\tnongit test_must_fail git hook run test-hook\n+'\n+\n+test_expect_success 'git -c core.hooksPath=<PATH> hook run' '\n+\tmkdir my-hooks &&\n+\twrite_script my-hooks/test-hook <<-\\EOF &&\n+\techo Hook ran $1 >>actual\n+\tEOF\n+\n+\tcat >expect <<-\\EOF &&\n+\tTest hook\n+\tHook ran one\n+\tHook ran two\n+\tHook ran three\n+\tHook ran four\n+\tEOF\n+\n+\t# Test various ways of specifying the path. See also\n+\t# t1350-config-hooks-path.sh\n+\t>actual &&\n+\tgit hook run test-hook -- ignored 2>>actual &&\n+\tgit -c core.hooksPath=my-hooks hook run test-hook -- one 2>>actual &&\n+\tgit -c core.hooksPath=my-hooks/ hook run test-hook -- two 2>>actual &&\n+\tgit -c core.hooksPath=\"$PWD/my-hooks\" hook run test-hook -- three 2>>actual &&\n+\tgit -c core.hooksPath=\"$PWD/my-hooks/\" hook run test-hook -- four 2>>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"438821","messageId":"patch-v2-02.13-e3dc0aed81b-20211015T093918Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v2-00.13-00000000000-20211015T093918Z-avarab@gmail.com","subject":"[PATCH v2 02/13] gc: use hook library for pre-auto-gc hook","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-15T09:43:30Z","receivedAt":"2021-10-15T09:43:54Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the pre-auto-gc hook away from run-command.h to and over to the\nnew hook.h library.\n\nTo do this introduce a simple run_hooks_oneshot() wrapper, we'll be\nusing it extensively for these simple cases of wanting to run a single\nhook under a given name, and having it free the memory we allocate for\nus.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/gc.c |  3 ++-\n hook.c       | 20 ++++++++++++++++++++\n hook.h       | 13 +++++++++++++\n 3 files changed, 35 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 6b3de3dd514..95939e77f53 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@@ -394,7 +395,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_oneshot(\"pre-auto-gc\", NULL))\n \t\treturn 0;\n \treturn 1;\n }\ndiff --git a/hook.c b/hook.c\nindex 20a1a4fec33..9951015dc66 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -131,3 +131,23 @@ int run_hooks(const char *hook_name, const char *hook_path,\n \n \treturn cb_data.rc;\n }\n+\n+int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options)\n+{\n+\tconst char *hook_path;\n+\tstruct run_hooks_opt hook_opt_scratch = RUN_HOOKS_OPT_INIT;\n+\tint ret = 0;\n+\n+\tif (!options)\n+\t\toptions = &hook_opt_scratch;\n+\n+\thook_path = find_hook(hook_name);\n+\tif (!hook_path)\n+\t\tgoto cleanup;\n+\n+\tret = run_hooks(hook_name, hook_path, options);\n+cleanup:\n+\trun_hooks_opt_clear(options);\n+\n+\treturn ret;\n+}\ndiff --git a/hook.h b/hook.h\nindex a9317c3f95e..9e4d10df63e 100644\n--- a/hook.h\n+++ b/hook.h\n@@ -44,7 +44,20 @@ void run_hooks_opt_clear(struct run_hooks_opt *o);\n /**\n  * Takes an already resolved hook found via find_hook() and runs\n  * it. Does not call run_hooks_opt_clear() for you.\n+ *\n+ * See run_hooks_oneshot() for the simpler one-shot API.\n  */\n int run_hooks(const char *hookname, const char *hook_path,\n \t      struct run_hooks_opt *options);\n+\n+/**\n+ * Calls find_hook() on your \"hook_name\" and runs the hooks (if any)\n+ * with run_hooks().\n+ *\n+ * If \"options\" is provided calls run_hooks_opt_clear() on it for\n+ * you. If \"options\" is NULL the default options from\n+ * RUN_HOOKS_OPT_INIT will be used.\n+ */\n+int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options);\n+\n #endif\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"438822","messageId":"patch-v2-04.13-0e34eb54054-20211015T093918Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v2-00.13-00000000000-20211015T093918Z-avarab@gmail.com","subject":"[PATCH v2 04/13] am: convert applypatch to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-15T09:43:32Z","receivedAt":"2021-10-15T09:43:55Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nTeach pre-applypatch, post-applypatch, and applypatch-msg to use the\nhook.h library instead of the run-command.h library.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/am.c | 8 +++++---\n 1 file changed, 5 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..5528f131a81 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -446,9 +446,11 @@ 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;\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_oneshot(\"applypatch-msg\", &opt);\n \n \tif (!ret) {\n \t\tFREE_AND_NULL(state->msg);\n@@ -1609,7 +1611,7 @@ static void do_commit(const struct am_state *state)\n \tconst char *reflog_msg, *author, *committer = NULL;\n \tstruct strbuf sb = STRBUF_INIT;\n \n-\tif (run_hook_le(NULL, \"pre-applypatch\", NULL))\n+\tif (run_hooks_oneshot(\"pre-applypatch\", NULL))\n \t\texit(1);\n \n \tif (write_cache_as_tree(&tree, 0, NULL))\n@@ -1661,7 +1663,7 @@ 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_oneshot(\"post-applypatch\", NULL);\n \n \tstrbuf_release(&sb);\n }\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"438823","messageId":"patch-v2-05.13-a4df96c1719-20211015T093918Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v2-00.13-00000000000-20211015T093918Z-avarab@gmail.com","subject":"[PATCH v2 05/13] hooks: convert 'post-checkout' hook to hook library","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-15T09:43:33Z","receivedAt":"2021-10-15T09:43:58Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the running of the 'post-checkout' hook away from run-command.h\nto the new hook.h library. For \"worktree\" this requires a change to it\nto run the hooks from a given directory.\n\nWe could strictly speaking skip the \"absolute_path\" flag and just\ncheck if \"dir\" is specified, but let's split them up for clarity, as\nwell as for any future user who'd like to set \"dir\" but not implicitly\nchange the argument to an absolute path.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/checkout.c | 14 +++++++++-----\n builtin/clone.c    |  7 +++++--\n builtin/worktree.c | 28 ++++++++++++----------------\n hook.c             | 10 ++++++++++\n hook.h             |  9 +++++++++\n read-cache.c       |  1 +\n reset.c            | 14 ++++++++++----\n 7 files changed, 56 insertions(+), 27 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex cbf73b8c9f6..54cbba821a8 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@@ -106,13 +107,16 @@ 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;\n+\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+\treturn run_hooks_oneshot(\"post-checkout\", &opt);\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 559acf9e036..53ca5ca0eb2 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@@ -659,6 +660,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;\n \n \tif (option_no_checkout)\n \t\treturn 0;\n@@ -705,8 +707,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),\n+\t\t     \"1\", NULL);\n+\terr |= run_hooks_oneshot(\"post-checkout\", &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 d22ece93e1a..330867c19bf 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -382,22 +382,18 @@ 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;\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+\t\topt.absolute_path = 1;\n+\n+\t\tret = run_hooks_oneshot(\"post-checkout\", &opt);\n \t}\n \n \tstrvec_clear(&child_env);\ndiff --git a/hook.c b/hook.c\nindex 9951015dc66..756f549e5b2 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -63,6 +63,7 @@ static int pick_next_hook(struct child_process *cp,\n \tcp->env = hook_cb->options->env.v;\n \tcp->stdout_to_stderr = 1;\n \tcp->trace2_hook_name = hook_cb->hook_name;\n+\tcp->dir = hook_cb->options->dir;\n \n \tstrvec_push(&cp->args, hook_path);\n \tstrvec_pushv(&cp->args, hook_cb->options->args.v);\n@@ -110,6 +111,7 @@ static int notify_hook_finished(int result,\n int run_hooks(const char *hook_name, const char *hook_path,\n \t      struct run_hooks_opt *options)\n {\n+\tstruct strbuf abs_path = STRBUF_INIT;\n \tstruct hook_cb_data cb_data = {\n \t\t.rc = 0,\n \t\t.hook_name = hook_name,\n@@ -121,6 +123,11 @@ int run_hooks(const char *hook_name, const char *hook_path,\n \tif (!options)\n \t\tBUG(\"a struct run_hooks_opt must be provided to run_hooks\");\n \n+\tif (options->absolute_path) {\n+\t\tstrbuf_add_absolute_path(&abs_path, hook_path);\n+\t\thook_path = abs_path.buf;\n+\t}\n+\n \trun_processes_parallel_tr2(jobs,\n \t\t\t\t   pick_next_hook,\n \t\t\t\t   notify_start_failure,\n@@ -129,6 +136,9 @@ int run_hooks(const char *hook_name, const char *hook_path,\n \t\t\t\t   \"hook\",\n \t\t\t\t   hook_name);\n \n+\tif (options->absolute_path)\n+\t\tstrbuf_release(&abs_path);\n+\n \treturn cb_data.rc;\n }\n \ndiff --git a/hook.h b/hook.h\nindex 9e4d10df63e..252a605b125 100644\n--- a/hook.h\n+++ b/hook.h\n@@ -9,6 +9,15 @@ struct run_hooks_opt\n \n \t/* Args to be passed to each hook */\n \tstruct strvec args;\n+\n+\t/*\n+\t * Resolve and run the \"absolute_path(hook)\" instead of\n+\t * \"hook\". Used for \"git worktree\" hooks\n+\t */\n+\tint absolute_path;\n+\n+\t/* Path to initial working directory for subprocess */\n+\tconst char *dir;\n };\n \n #define RUN_HOOKS_OPT_INIT { \\\ndiff --git a/read-cache.c b/read-cache.c\nindex a78b88a41bf..9773118d078 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -28,6 +28,7 @@\n #include \"sparse-index.h\"\n #include \"csum-file.h\"\n #include \"promisor-remote.h\"\n+#include \"hook.h\"\n \n /* Mask for the name length in ce_flags in the on-disk index */\n \ndiff --git a/reset.c b/reset.c\nindex f214df3d96c..4234dd345db 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,15 @@ 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;\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_oneshot(\"post-checkout\", &opt);\n+\t}\n \n leave_reset_head:\n \tstrbuf_release(&msg);\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"438824","messageId":"patch-v2-03.13-6227a1e644d-20211015T093918Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v2-00.13-00000000000-20211015T093918Z-avarab@gmail.com","subject":"[PATCH v2 03/13] rebase: convert pre-rebase to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-15T09:43:31Z","receivedAt":"2021-10-15T09:43:58Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the pre-rebase hook away from run-command.h to and over to the\nnew hook.h library.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/rebase.c | 8 ++++++--\n 1 file changed, 6 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 8c6393f6d78..9a44939d662 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@@ -1305,6 +1306,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;\n \tstruct option builtin_rebase_options[] = {\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n@@ -2021,9 +2023,11 @@ 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_push(&hook_opt.args, options.upstream_arg);\n+\tif (argc)\n+\t\tstrvec_push(&hook_opt.args, argv[0]);\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_oneshot(\"pre-rebase\", &hook_opt))\n \t\tdie(_(\"The pre-rebase hook refused to rebase.\"));\n \n \tif (options.flags & REBASE_DIFFSTAT) {\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"438825","messageId":"patch-v2-06.13-327f916f8c3-20211015T093918Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v2-00.13-00000000000-20211015T093918Z-avarab@gmail.com","subject":"[PATCH v2 06/13] merge: convert post-merge to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-15T09:43:34Z","receivedAt":"2021-10-15T09:43:59Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nTeach post-merge to use the hook.h library instead of the\nrun-command.h library to run hooks.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/merge.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex cc4a910c69b..c48b875b82b 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -448,6 +448,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;\n \tconst struct object_id *head = &head_commit->object.oid;\n \n \tif (!msg)\n@@ -488,7 +489,8 @@ 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_oneshot(\"post-merge\", &opt);\n \n \tapply_autostash(git_path_merge_autostash(the_repository));\n \tstrbuf_release(&reflog_message);\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"438826","messageId":"patch-v2-07.13-328767015b1-20211015T093918Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v2-00.13-00000000000-20211015T093918Z-avarab@gmail.com","subject":"[PATCH v2 07/13] git hook run: add an --ignore-missing flag","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-15T09:43:35Z","receivedAt":"2021-10-15T09:44:01Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"For certain one-shot hooks we'd like to optimistically run them, and\nnot complain if they don't exist. This will be used by send-email in a\nsubsequent commit.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Documentation/git-hook.txt | 10 +++++++++-\n builtin/hook.c             |  7 ++++++-\n t/t1800-hook.sh            |  5 +++++\n 3 files changed, 20 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt\nindex e39b1b5d069..77c3a8ad909 100644\n--- a/Documentation/git-hook.txt\n+++ b/Documentation/git-hook.txt\n@@ -8,7 +8,7 @@ git-hook - Run git hooks\n SYNOPSIS\n --------\n [verse]\n-'git hook' run <hook-name> [-- <hook-args>]\n+'git hook' run [--ignore-missing] <hook-name> [-- <hook-args>]\n \n DESCRIPTION\n -----------\n@@ -28,6 +28,14 @@ Any positional arguments to the hook should be passed after a\n mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See\n linkgit:githooks[5] for arguments hooks might expect (if any).\n \n+OPTIONS\n+-------\n+\n+--ignore-missing::\n+\tIgnore any missing hook by quietly returning zero. Used for\n+\ttools that want to do a blind one-shot run of a hook that may\n+\tor may not be present.\n+\n SEE ALSO\n --------\n linkgit:githooks[5]\ndiff --git a/builtin/hook.c b/builtin/hook.c\nindex 41dd15550cf..fa26454990d 100644\n--- a/builtin/hook.c\n+++ b/builtin/hook.c\n@@ -7,7 +7,7 @@\n #include \"strvec.h\"\n \n #define BUILTIN_HOOK_RUN_USAGE \\\n-\tN_(\"git hook run <hook-name> [-- <hook-args>]\")\n+\tN_(\"git hook run [--ignore-missing] <hook-name> [-- <hook-args>]\")\n \n static const char * const builtin_hook_usage[] = {\n \tBUILTIN_HOOK_RUN_USAGE,\n@@ -23,9 +23,12 @@ static int run(int argc, const char **argv, const char *prefix)\n {\n \tint i;\n \tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n+\tint ignore_missing = 0;\n \tconst char *hook_name;\n \tconst char *hook_path;\n \tstruct option run_options[] = {\n+\t\tOPT_BOOL(0, \"ignore-missing\", &ignore_missing,\n+\t\t\t N_(\"silently ignore missing requested <hook-name>\")),\n \t\tOPT_END(),\n \t};\n \tint ret;\n@@ -53,6 +56,8 @@ static int run(int argc, const char **argv, const char *prefix)\n \tgit_config(git_default_config, NULL);\n \n \thook_name = argv[0];\n+\tif (ignore_missing)\n+\t\treturn run_hooks_oneshot(hook_name, &opt);\n \thook_path = find_hook(hook_name);\n \tif (!hook_path) {\n \t\terror(\"cannot find a hook named %s\", hook_name);\ndiff --git a/t/t1800-hook.sh b/t/t1800-hook.sh\nindex 3aea1b105f0..29718aa9913 100755\n--- a/t/t1800-hook.sh\n+++ b/t/t1800-hook.sh\n@@ -21,6 +21,11 @@ test_expect_success 'git hook run: nonexistent hook' '\n \ttest_cmp stderr.expect stderr.actual\n '\n \n+test_expect_success 'git hook run: nonexistent hook with --ignore-missing' '\n+\tgit hook run --ignore-missing does-not-exist 2>stderr.actual &&\n+\ttest_must_be_empty stderr.actual\n+'\n+\n test_expect_success 'git hook run: basic' '\n \twrite_script .git/hooks/test-hook <<-EOF &&\n \techo Test hook\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"438827","messageId":"patch-v2-08.13-6c4ebd68d56-20211015T093918Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v2-00.13-00000000000-20211015T093918Z-avarab@gmail.com","subject":"[PATCH v2 08/13] send-email: use 'git hook run' for 'sendemail-validate'","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-15T09:43:36Z","receivedAt":"2021-10-15T09:44:02Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nChange the \"sendmail-validate\" hook to be run via the \"git hook run\"\nwrapper instead of via a direct invocation.\n\nThis is the smallest possibly change to get \"send-email\" using \"git\nhook run\". We still check the hook itself with \"-x\", and set a\n\"GIT_DIR\" variable, both of which are asserted by our tests. We'll\nneed to get rid of this special behavior if we start running N hooks,\nbut for now let's be as close to bug-for-bug compatible as possible.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl   | 22 ++++++++++++++--------\n t/t9001-send-email.sh |  4 ++--\n 2 files changed, 16 insertions(+), 10 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 5262d88ee32..4c20c0bbb79 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -195,13 +195,13 @@ sub format_2822_time {\n my $editor;\n \n sub system_or_msg {\n-\tmy ($args, $msg) = @_;\n+\tmy ($args, $msg, $cmd_name) = @_;\n \tsystem(@$args);\n \tmy $signalled = $? & 127;\n \tmy $exit_code = $? >> 8;\n \treturn unless $signalled or $exit_code;\n \n-\tmy @sprintf_args = ($args->[0], $exit_code);\n+\tmy @sprintf_args = ($cmd_name ? $cmd_name : $args->[0], $exit_code);\n \tif (defined $msg) {\n \t\t# Quiet the 'redundant' warning category, except we\n \t\t# need to support down to Perl 5.8, so we can't do a\n@@ -2039,10 +2039,10 @@ sub validate_patch {\n \tmy ($fn, $xfer_encoding) = @_;\n \n \tif ($repo) {\n+\t\tmy $hook_name = 'sendemail-validate';\n \t\tmy $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks');\n \t\trequire File::Spec;\n-\t\tmy $validate_hook = File::Spec->catfile($hooks_path,\n-\t\t\t\t\t    'sendemail-validate');\n+\t\tmy $validate_hook = File::Spec->catfile($hooks_path, $hook_name);\n \t\tmy $hook_error;\n \t\tif (-x $validate_hook) {\n \t\t\trequire Cwd;\n@@ -2052,13 +2052,19 @@ sub validate_patch {\n \t\t\tchdir($repo->wc_path() or $repo->repo_path())\n \t\t\t\tor die(\"chdir: $!\");\n \t\t\tlocal $ENV{\"GIT_DIR\"} = $repo->repo_path();\n-\t\t\t$hook_error = system_or_msg([$validate_hook, $target]);\n+\t\t\tmy @cmd = (\"git\", \"hook\", \"run\", \"--ignore-missing\",\n+\t\t\t\t    $hook_name, \"--\");\n+\t\t\tmy @cmd_msg = (@cmd, \"<patch>\");\n+\t\t\tmy @cmd_run = (@cmd, $target);\n+\t\t\t$hook_error = system_or_msg(\\@cmd_run, undef, \"@cmd_msg\");\n \t\t\tchdir($cwd_save) or die(\"chdir: $!\");\n \t\t}\n \t\tif ($hook_error) {\n-\t\t\tdie sprintf(__(\"fatal: %s: rejected by sendemail-validate hook\\n\" .\n-\t\t\t\t       \"%s\\n\" .\n-\t\t\t\t       \"warning: no patches were sent\\n\"), $fn, $hook_error);\n+\t\t\t$hook_error = sprintf(__(\"fatal: %s: rejected by %s hook\\n\" .\n+\t\t\t\t\t\t $hook_error . \"\\n\" .\n+\t\t\t\t\t\t \"warning: no patches were sent\\n\"),\n+\t\t\t\t\t      $fn, $hook_name);\n+\t\t\tdie $hook_error;\n \t\t}\n \t}\n \ndiff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\nindex aa0c20499ba..84d0f40d76a 100755\n--- a/t/t9001-send-email.sh\n+++ b/t/t9001-send-email.sh\n@@ -539,7 +539,7 @@ test_expect_success $PREREQ \"--validate respects relative core.hooksPath path\" '\n \ttest_path_is_file my-hooks.ran &&\n \tcat >expect <<-EOF &&\n \tfatal: longline.patch: rejected by sendemail-validate hook\n-\tfatal: command '\"'\"'my-hooks/sendemail-validate'\"'\"' died with exit code 1\n+\tfatal: command '\"'\"'git hook run --ignore-missing sendemail-validate -- <patch>'\"'\"' died with exit code 1\n \twarning: no patches were sent\n \tEOF\n \ttest_cmp expect actual\n@@ -558,7 +558,7 @@ test_expect_success $PREREQ \"--validate respects absolute core.hooksPath path\" '\n \ttest_path_is_file my-hooks.ran &&\n \tcat >expect <<-EOF &&\n \tfatal: longline.patch: rejected by sendemail-validate hook\n-\tfatal: command '\"'\"'$hooks_path/sendemail-validate'\"'\"' died with exit code 1\n+\tfatal: command '\"'\"'git hook run --ignore-missing sendemail-validate -- <patch>'\"'\"' died with exit code 1\n \twarning: no patches were sent\n \tEOF\n \ttest_cmp expect actual\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"438828","messageId":"patch-v2-09.13-b1f52733e3c-20211015T093918Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v2-00.13-00000000000-20211015T093918Z-avarab@gmail.com","subject":"[PATCH v2 09/13] git-p4: use 'git hook' to run hooks","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-15T09:43:37Z","receivedAt":"2021-10-15T09:44:04Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nInstead of duplicating the behavior of run-command.h:run_hook_le() in\nPython, we can directly call 'git hook run'. We emulate the existence\ncheck with the --ignore-missing flag.\n\nAs this is the last hook execution in git.git to not go through \"git\nhook run\" or the hook.[ch] library we can now be absolutely sure that\nour assertion in hook.c that only hooks known by the generated (from\ngithooks(5)) hook-list.h are permitted.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-p4.py | 72 ++++++-------------------------------------------------\n 1 file changed, 7 insertions(+), 65 deletions(-)\n\ndiff --git a/git-p4.py b/git-p4.py\nindex 2b4500226aa..1f24cbf0bca 100755\n--- a/git-p4.py\n+++ b/git-p4.py\n@@ -207,71 +207,13 @@ def decode_path(path):\n         return 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-        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-\n+    \"\"\"args are specified with -a <arg> -a <arg> -a <arg>\"\"\"\n+    args = ['git', 'hook', 'run', '--ignore-missing', cmd]\n+    if param:\n+        args.append(\"--\")\n+        for p in param:\n+            args.append(p)\n+    return subprocess.call(args) == 0\n \n def write_pipe(c, stdin):\n     if verbose:\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"438829","messageId":"patch-v2-10.13-dc31d98acdf-20211015T093918Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v2-00.13-00000000000-20211015T093918Z-avarab@gmail.com","subject":"[PATCH v2 10/13] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-15T09:43:38Z","receivedAt":"2021-10-15T09:44:06Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove these hooks hook away from run-command.h to and over to the new\nhook.h library.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n commit.c | 15 ++++++++-------\n 1 file changed, 8 insertions(+), 7 deletions(-)\n\ndiff --git a/commit.c b/commit.c\nindex 551de4903c1..4e7cbd7585d 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@@ -1700,22 +1701,22 @@ size_t ignore_non_trailer(const char *buf, size_t len)\n int run_commit_hook(int editor_is_used, 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;\n \tva_list args;\n-\tint ret;\n+\tconst char *arg;\n \n-\tstrvec_pushf(&hook_env, \"GIT_INDEX_FILE=%s\", index_file);\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-\treturn ret;\n+\treturn run_hooks_oneshot(name, &opt);\n }\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"438830","messageId":"patch-v2-11.13-58b7689e4af-20211015T093918Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v2-00.13-00000000000-20211015T093918Z-avarab@gmail.com","subject":"[PATCH v2 11/13] read-cache: convert post-index-change to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-15T09:43:39Z","receivedAt":"2021-10-15T09:44:09Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the post-index-change hook away from run-command.h to and over to\nthe new hook.h library.\n\nThis removes the last direct user of \"run_hook_ve()\" outside of\nrun-command.c (\"run_hook_le()\" still uses it). So we can make the\nfunction static now. A subsequent commit will remove this code\nentirely when \"run_hook_le()\" itself goes away.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n read-cache.c  | 10 +++++++---\n run-command.c |  2 +-\n run-command.h |  1 -\n 3 files changed, 8 insertions(+), 5 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex 9773118d078..84cc3d88196 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -3087,6 +3087,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n {\n \tint ret;\n \tint was_full = !istate->sparse_index;\n+\tstruct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;\n \n \tret = convert_to_sparse(istate, 0);\n \n@@ -3115,9 +3116,12 @@ 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_oneshot(\"post-index-change\", &hook_opt);\n+\n \tistate->updated_workdir = 0;\n \tistate->updated_skipworktree = 0;\n \ndiff --git a/run-command.c b/run-command.c\nindex 7ef5cc712a9..d92e670c8ed 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -1323,7 +1323,7 @@ int async_with_fork(void)\n #endif\n }\n \n-int run_hook_ve(const char *const *env, const char *name, va_list args)\n+static 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;\ndiff --git a/run-command.h b/run-command.h\nindex 49878262584..3fa7454cf8a 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -239,7 +239,6 @@ int run_command(struct child_process *);\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-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"438831","messageId":"patch-v2-12.13-ae1e2a82147-20211015T093918Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v2-00.13-00000000000-20211015T093918Z-avarab@gmail.com","subject":"[PATCH v2 12/13] receive-pack: convert push-to-checkout hook to hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-15T09:43:40Z","receivedAt":"2021-10-15T09:44:14Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the push-to-checkout hook away from run-command.h to and over to\nthe new hook.h library.\n\nThis removes the last direct user of run_hook_le(), so we could remove\nthat function now, but let's leave that to a follow-up cleanup commit.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/receive-pack.c | 7 +++++--\n 1 file changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 25cc0c907e1..22e7a431728 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1435,9 +1435,12 @@ 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;\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_oneshot(push_to_checkout_hook, &opt))\n \t\treturn \"push-to-checkout hook declined\";\n \telse\n \t\treturn NULL;\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"438832","messageId":"patch-v2-13.13-289d5a2d849-20211015T093918Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v2-00.13-00000000000-20211015T093918Z-avarab@gmail.com","subject":"[PATCH v2 13/13] run-command: remove old run_hook_{le,ve}() hook API","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-15T09:43:41Z","receivedAt":"2021-10-15T09:44:18Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nThe new hook.h library has replaced all run-command.h hook-related\nfunctionality. So let's delete this dead code.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n run-command.c | 32 --------------------------------\n run-command.h | 16 ----------------\n 2 files changed, 48 deletions(-)\n\ndiff --git a/run-command.c b/run-command.c\nindex d92e670c8ed..8a21ff525f3 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -1323,38 +1323,6 @@ int async_with_fork(void)\n #endif\n }\n \n-static 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 3fa7454cf8a..59e1fbff64c 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -224,22 +224,6 @@ int finish_command_in_signal(struct child_process *);\n  */\n int run_command(struct child_process *);\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-\n /*\n  * Trigger an auto-gc\n  */\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"438865","messageId":"YWmrAuUeO38Tf/W+@google.com","threadId":"56717","inReplyTo":"cover-v2-00.13-00000000000-20211015T093918Z-avarab@gmail.com","subject":"Re: [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2021-10-15T16:23:30Z","receivedAt":"2021-10-15T16:23:39Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Tue, Oct 12, 2021 at 03:30:25PM +0200, Ævar Arnfjörð Bjarmason wrote:\n> \n> This series is part 2 of an incremental restart of the greater\n> \"config-based hooks\" topic by Emily Shaffer that I've been helping\n> with. See [1] for part 1, and [2] (search for \"mark2\") for a summary\n> of parts to come.\n> \n> This goes on top of the \"ab/config-based-hooks-1\" topic that's already\n> in \"next\" and marked for graduation to \"master\".\n> \n> In this topic we build upon the skeleton hook.[ch] library and build\n> infrastructure added in part 1 and add a hook running library and new\n> \"git hook\" command.\n> \n> The new \"git hook\" command is (for now) only for the benefit of\n> in-tree non-C programs that need to run hooks, and this series\n> converts git-send-email and git-p4 to use it.\n> \n> At the end of the series we remove\n> \"run_hook_{le,ve}()\" from run-command.c, as we've migrated all its\n> callers.\n> \n> What we don't do is convert any of the more complex in-tree hooks that\n> require input on stdin such as \"pre-receive\" and the like, that's in\n> later parts once this lands.\n> \n> This series is approximately patch 6-20/36 of the previous 36 patch\n> ab/config-based-hooks-base topic. A range-diff to that v5[3] is\n> included below.\n> \n> The changes since that v5 are rather trivial, they are:\n> \n>  * Formatting changes to reduce the diff to parts that come after this\n>    (which Emily & I were juggling at the time), and re-flowing some\n>    overly long lines.\n> \n>  * The new test is now marked for SANITIZE=leak testing, with the new\n>    test facility I've added recently (and which just landed in\n>    \"master\"). They all pass, the new API doesn't leak.\n> \n>  * I rewrote some of the git-send-email.perl code to avoid\n>    de-duplication and hardcoding (just using intermediate variables).\n> \n> Things to focus on reviewing:\n> \n>  * This should all be pretty solid and well tested, but the git-p4.py\n>    part in particular I've never tested for real (not having access to\n>    p4), and think Emily hasn't either[4].\n\nCorrect. I seem to remember wanting to give this a test and not having access\nto real perforce to try it against. \n\n> \n>    The relevant patch looks trivially correct to me[5], and I've tried it\n>    out in the Python REPL. But if any of the CC'd people active in\n>    git-p4.py development could give it some end-to-end testing that\n>    would be much appreciated.\n> \n>  * Both Python and Perl code now calls the in-between \"git hook run\"\n>    command rather than calling hooks directly. Will this behave\n>    differently due to any special behavior running via a git built-in\n>    adds?\n> \n>    I vaguely recall a third-party \"git-foo\" program breaking in the\n>    past when invoked as \"git foo\" but not \"git-foo\", due to git\n>    squatting on SIGINT, but none of that should be relevant here\n>    (we're not starting a pager etc.).\n\nI would be very surprised, assuming that we invoke 'git hook' in the same way\nthat those Python and Perl scripts invoke other 'git whatever' commands...\n\nThat said, once we invoke a user hook, all bets are off as far as \"we're not\nstarting a pager or anything\". A determined user could find a reason to invoke\na pager, editor, whatever during a hook. I'm curious to know more about this\nremembered breakage.\n\nLast time I looked at the range-diff; this time I'll look at the individual\npatches, instead.\n\nNote: I looked at a bunch of these offline, so sorry for the sudden\ningress of mails today ;)\n\n - Emily\n"},{"id":"438866","messageId":"YWmrL/POUE8P8Od1@google.com","threadId":"56717","inReplyTo":"patch-v2-01.13-ba64faf0580-20211015T093918Z-avarab@gmail.com","subject":"Re: [PATCH v2 01/13] hook: add 'run' subcommand","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2021-10-15T16:24:15Z","receivedAt":"2021-10-15T16:24:23Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Tue, Oct 12, 2021 at 03:30:26PM +0200, Ævar Arnfjörð Bjarmason wrote:\n> \n> \n> In order to enable hooks to be run as an external process, by a\n> standalone Git command, or by tools which wrap Git, provide an external\n> means to run all configured hook commands for a given hook event.\n> \n> Most of our hooks require more complex functionality than this, but\n> let's start with the bare minimum required to support our simplest\n> hooks.\n> \n> In terms of implementation the usage_with_options() and \"goto usage\"\n> pattern here mirrors that of\n> builtin/{commit-graph,multi-pack-index}.c.\n> \n> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> ---\n> diff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt\n> new file mode 100644\n> index 00000000000..660d6a992a0\n> --- /dev/null\n> +++ b/Documentation/git-hook.txt\n> @@ -0,0 +1,38 @@\n> +git-hook(1)\n> +===========\n> +\n> +NAME\n> +----\n> +git-hook - run git hooks\n> +\n> +SYNOPSIS\n> +--------\n> +[verse]\n> +'git hook' run <hook-name> [-- <hook-args>]\n> +\n> +DESCRIPTION\n> +-----------\n> +\n> +This command is an interface to git hooks (see linkgit:githooks[5]).\n> +Currently it only provides a convenience wrapper for running hooks for\n> +use by git itself. In the future it might gain other functionality.\n\ns/git/Git/g nit, no? I thought we were somewhat picky about capitalizing Git\nwhen we don't mean `git`?\n\n> +\n> +SUBCOMMANDS\n> +-----------\n> +\n> +run::\n> +\tRun the `<hook-name>` hook. See linkgit:githooks[5] for\n> +\tthe hook names we support.\n> ++\n> +Any positional arguments to the hook should be passed after an\n> +optional `--` (or `--end-of-options`, see linkgit:gitcli[7]). The\n> +arguments (if any) differ by hook name, see linkgit:githooks[5] for\n> +what those are.\n> +\n> +SEE ALSO\n> +--------\n> +linkgit:githooks[5]\n> +\n> +GIT\n> +---\n> +Part of the linkgit:git[1] suite\n\n> diff --git a/builtin/hook.c b/builtin/hook.c\n> new file mode 100644\n> index 00000000000..012a2973b38\n> --- /dev/null\n> +++ b/builtin/hook.c\n> @@ -0,0 +1,90 @@\n> +#include \"cache.h\"\n> +#include \"builtin.h\"\n> +#include \"config.h\"\n> +#include \"hook.h\"\n> +#include \"parse-options.h\"\n> +#include \"strbuf.h\"\n> +#include \"strvec.h\"\n> +\n> +#define BUILTIN_HOOK_RUN_USAGE \\\n> +\tN_(\"git hook run <hook-name> [-- <hook-args>]\")\n> +\n> +static const char * const builtin_hook_usage[] = {\n> +\tBUILTIN_HOOK_RUN_USAGE,\n> +\tNULL\n> +};\n> +\n> +static const char * const builtin_hook_run_usage[] = {\n> +\tBUILTIN_HOOK_RUN_USAGE,\n> +\tNULL\n> +};\n\nWhile some reviewers might think this looks untidy, it does save us some\nrepetition later on adding other subcommands to 'git hook'. Thanks.\n\n> +\n> +static int run(int argc, const char **argv, const char *prefix)\n> +{\n> +\tint i;\n> +\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n> +\tconst char *hook_name;\n> +\tconst char *hook_path;\n> +\tstruct option run_options[] = {\n> +\t\tOPT_END(),\n> +\t};\n> +\tint ret;\n> +\n> +\targc = parse_options(argc, argv, prefix, run_options,\n> +\t\t\t     builtin_hook_run_usage,\n> +\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n\nWe KEEP_DASHDASH so we can ensure someone did not send us some args they meant\nfor 'git hook' instead of for the hook script itself. Ok.\n\n> +\n> +\tif (!argc)\n> +\t\tgoto usage;\n> +\n> +\t/*\n> +\t * Having a -- for \"run\" when providing <hook-args> is\n> +\t * mandatory.\n> +\t */\n> +\tif (argc > 1 && strcmp(argv[1], \"--\") &&\n> +\t    strcmp(argv[1], \"--end-of-options\"))\n\n--end-of-options isn't documented anywhere, right? Fine, I don't mind, but it's\ninteresting to support it \"secretly\".\n\n> +\t\tgoto usage;\n> +\n> +\t/* Add our arguments, start after -- */\n> +\tfor (i = 2 ; i < argc; i++)\n> +\t\tstrvec_push(&opt.args, argv[i]);\n> +\n> +\t/* Need to take into account core.hooksPath */\n> +\tgit_config(git_default_config, NULL);\n\n..and git_default_config() sets it up for use in find_hook(). Great.\n\n> +\n> +\t/*\n> +\t * We are not using a plain run_hooks() because we'd like to\n> +\t * detect missing hooks. Let's find it ourselves and call\n> +\t * run_hooks() instead.\n> +\t */\n\nHmm. In practice, I wonder if this means that callers like 'git-send-email' still\nneed to check whether there is a 'sendemail-validate' hook before they can call\n'git hook run sendemail-validate'? I guess I'll have a look at the work there\nand see.\n\n> +\thook_name = argv[0];\n> +\thook_path = find_hook(hook_name);\n> +\tif (!hook_path) {\n> +\t\terror(\"cannot find a hook named %s\", hook_name);\n> +\t\treturn 1;\n> +\t}\n> +\n> +\tret = run_hooks(hook_name, hook_path, &opt);\n> +\trun_hooks_opt_clear(&opt);\n> +\treturn ret;\n> +usage:\n> +\tusage_with_options(builtin_hook_run_usage, run_options);\n> +}\n> +\n> +int cmd_hook(int argc, const char **argv, const char *prefix)\n> +{\n> +\tstruct option builtin_hook_options[] = {\n> +\t\tOPT_END(),\n> +\t};\n> +\n> +\targc = parse_options(argc, argv, NULL, builtin_hook_options,\n> +\t\t\t     builtin_hook_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n> +\tif (!argc)\n> +\t\tgoto usage;\n> +\n> +\tif (!strcmp(argv[0], \"run\"))\n> +\t\treturn run(argc, argv, prefix);\n> +\n> +usage:\n> +\tusage_with_options(builtin_hook_usage, builtin_hook_options);\n> +}\n\nThe rest of it is very straightforward boilerplate for a builtin. Thanks.\n\n\n> diff --git a/command-list.txt b/command-list.txt\n> index a289f09ed6f..9ccd8e5aebe 100644\n> --- a/command-list.txt\n> +++ b/command-list.txt\n> @@ -103,6 +103,7 @@ git-grep                                mainporcelain           info\n>  git-gui                                 mainporcelain\n>  git-hash-object                         plumbingmanipulators\n>  git-help                                ancillaryinterrogators          complete\n> +git-hook                                mainporcelain\n>  git-http-backend                        synchingrepositories\n>  git-http-fetch                          synchelpers\n>  git-http-push                           synchelpers\n> diff --git a/git.c b/git.c\n> index 60c2784be45..e5891e02021 100644\n> --- a/git.c\n> +++ b/git.c\n> @@ -538,6 +538,7 @@ static struct cmd_struct commands[] = {\n>  \t{ \"grep\", cmd_grep, RUN_SETUP_GENTLY },\n>  \t{ \"hash-object\", cmd_hash_object },\n>  \t{ \"help\", cmd_help },\n> +\t{ \"hook\", cmd_hook, RUN_SETUP },\n\nOk. For now this is still requiring a gitdir. That makes sense to me, because\nwe are still getting our hooks from $GITDIR/hooks/. Fine.\n\n>  \t{ \"index-pack\", cmd_index_pack, RUN_SETUP_GENTLY | NO_PARSEOPT },\n>  \t{ \"init\", cmd_init_db },\n>  \t{ \"init-db\", cmd_init_db },\n> diff --git a/hook.c b/hook.c\n> index 55e1145a4b7..240270db64e 100644\n> --- a/hook.c\n> +++ b/hook.c\n> @@ -1,6 +1,7 @@\n>  #include \"cache.h\"\n>  #include \"hook.h\"\n>  #include \"run-command.h\"\n> +#include \"config.h\"\n>  \n>  const char *find_hook(const char *name)\n>  {\n> @@ -40,3 +41,103 @@ int hook_exists(const char *name)\n>  {\n>  \treturn !!find_hook(name);\n>  }\n> +\n> +void run_hooks_opt_clear(struct run_hooks_opt *o)\n> +{\n> +\tstrvec_clear(&o->env);\n> +\tstrvec_clear(&o->args);\n> +}\n> +\n> +static int pick_next_hook(struct child_process *cp,\n> +\t\t\t  struct strbuf *out,\n> +\t\t\t  void *pp_cb,\n> +\t\t\t  void **pp_task_cb)\n> +{\n> +\tstruct hook_cb_data *hook_cb = pp_cb;\n> +\tstruct hook *run_me = hook_cb->run_me;\n> +\n> +\tif (!run_me)\n> +\t\treturn 0;\n\nSignal the run_processes_parallel() machinery if we don't have any hooks left\nto run. Right now the linked list is always going to be of size 1, but that's\nok :)\n\n> +\n> +\tcp->no_stdin = 1;\n> +\tcp->env = hook_cb->options->env.v;\n> +\tcp->stdout_to_stderr = 1;\n> +\tcp->trace2_hook_name = hook_cb->hook_name;\n\nThese args match identically the ones set up in run_hook_ve. Ok.\n\n> +\n> +\t/* add command */\n> +\tstrvec_push(&cp->args, run_me->hook_path);\n> +\n> +\t/*\n> +\t * add passed-in argv, without expanding - let the user get back\n> +\t * exactly what they put in\n> +\t */\n> +\tstrvec_pushv(&cp->args, hook_cb->options->args.v);\n> +\n> +\t/* Provide context for errors if necessary */\n> +\t*pp_task_cb = run_me;\n> +\n> +\t/*\n> +\t * This pick_next_hook() will be called again, we're only\n> +\t * running one hook, so indicate that no more work will be\n> +\t * done.\n> +\t */\n> +\thook_cb->run_me = NULL;\n> +\n> +\treturn 1;\n> +}\n> +\n> +static int notify_start_failure(struct strbuf *out,\n> +\t\t\t\tvoid *pp_cb,\n> +\t\t\t\tvoid *pp_task_cp)\n> +{\n> +\tstruct hook_cb_data *hook_cb = pp_cb;\n> +\tstruct hook *attempted = pp_task_cp;\n> +\n> +\thook_cb->rc |= 1;\n\nThe OR means this will continue to work if the list of hooks is greater than 1.\nOk.\n\n> +\n> +\tstrbuf_addf(out, _(\"Couldn't start hook '%s'\\n\"),\n> +\t\t    attempted->hook_path);\n> +\n> +\treturn 1;\n\nBy returning 1, we ask the run_processes_parallel() machinery to give up on\nthe rest of the hooks. I think that is okay, because the Git operation will not\ncomplete successfully anyway, and because \"failed to start process\" has more\nserious implications for Git's ability to continue starting tasks, compared to\na hook validation failure. Ok.\n\n> +}\n> +\n> +static int notify_hook_finished(int result,\n> +\t\t\t\tstruct strbuf *out,\n> +\t\t\t\tvoid *pp_cb,\n> +\t\t\t\tvoid *pp_task_cb)\n> +{\n> +\tstruct hook_cb_data *hook_cb = pp_cb;\n> +\n> +\thook_cb->rc |= result;\n> +\n> +\treturn 0;\n\nConversely, here we could decide to terminate the task list early if we heard\nan unhappy hook's return code. However, it's probably more useful for a user\nto hear *all* the reasons that their operation failed, rather than just one,\nso they don't need to rerun their commit operation 5 times for 5 different hook\nfailures... :) Fine.\n\n> +}\n> +\n> +int run_hooks(const char *hook_name, const char *hook_path,\n> +\t      struct run_hooks_opt *options)\n> +{\n> +\tstruct hook my_hook = {\n> +\t\t.hook_path = hook_path,\n> +\t};\n> +\tstruct hook_cb_data cb_data = {\n> +\t\t.rc = 0,\n> +\t\t.hook_name = hook_name,\n> +\t\t.options = options,\n> +\t};\n> +\tint jobs = 1;\n\nEven though this is a constant, I find the throwaway variable easier to read\nthan using a magic 1 in the run_processes_parallel call. I imagine the compiler\ngets rid of it, anyways. Thanks.\n\n> +\n> +\tif (!options)\n> +\t\tBUG(\"a struct run_hooks_opt must be provided to run_hooks\");\n> +\n> +\tcb_data.run_me = &my_hook;\n> +\n> +\trun_processes_parallel_tr2(jobs,\n> +\t\t\t\t   pick_next_hook,\n> +\t\t\t\t   notify_start_failure,\n> +\t\t\t\t   notify_hook_finished,\n> +\t\t\t\t   &cb_data,\n> +\t\t\t\t   \"hook\",\n> +\t\t\t\t   hook_name);\n> +\n> +\treturn cb_data.rc;\n> +}\n> diff --git a/hook.h b/hook.h\n> index 6aa36fc7ff9..111c5cf9296 100644\n> --- a/hook.h\n> +++ b/hook.h\n> @@ -1,5 +1,33 @@\n>  #ifndef HOOK_H\n>  #define HOOK_H\n> +#include \"strvec.h\"\n> +\n> +struct hook {\n> +\t/* The path to the hook */\n> +\tconst char *hook_path;\n> +};\n> +\n> +struct run_hooks_opt\n> +{\n> +\t/* Environment vars to be set for each hook */\n> +\tstruct strvec env;\n> +\n> +\t/* Args to be passed to each hook */\n> +\tstruct strvec args;\n> +};\n> +\n> +#define RUN_HOOKS_OPT_INIT { \\\n> +\t.env = STRVEC_INIT, \\\n> +\t.args = STRVEC_INIT, \\\n> +}\n> +\n> +struct hook_cb_data {\n> +\t/* rc reflects the cumulative failure state */\n> +\tint rc;\n> +\tconst char *hook_name;\n> +\tstruct hook *run_me;\n> +\tstruct run_hooks_opt *options;\n> +};\n>  \n>  /*\n>   * Returns the path to the hook file, or NULL if the hook is missing\n> @@ -13,4 +41,15 @@ const char *find_hook(const char *name);\n>   */\n>  int hook_exists(const char *hookname);\n>  \n> +/**\n> + * Clear data from an initialized \"struct run_hooks-opt\".\nNit, s/run_hooks-opt/run_hooks_opt/\n> + */\n> +void run_hooks_opt_clear(struct run_hooks_opt *o);\n> +\n> +/**\n> + * Takes an already resolved hook found via find_hook() and runs\n> + * it. Does not call run_hooks_opt_clear() for you.\n> + */\n> +int run_hooks(const char *hookname, const char *hook_path,\n> +\t      struct run_hooks_opt *options);\n>  #endif\n> diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh\n> new file mode 100755\n> index 00000000000..3aea1b105f0\n> --- /dev/null\n> +++ b/t/t1800-hook.sh\n> @@ -0,0 +1,129 @@\n> +#!/bin/sh\n> +\n> +test_description='git-hook command'\n> +\n> +TEST_PASSES_SANITIZE_LEAK=true\n> +. ./test-lib.sh\n> +\n> +test_expect_success 'git hook usage' '\n> +\ttest_expect_code 129 git hook &&\n> +\ttest_expect_code 129 git hook run &&\n> +\ttest_expect_code 129 git hook run -h &&\n> +\ttest_expect_code 129 git hook run --unknown 2>err &&\n> +\tgrep \"unknown option\" err\n> +'\n> +\n> +test_expect_success 'git hook run: nonexistent hook' '\n> +\tcat >stderr.expect <<-\\EOF &&\n> +\terror: cannot find a hook named test-hook\n> +\tEOF\n> +\ttest_expect_code 1 git hook run test-hook 2>stderr.actual &&\n> +\ttest_cmp stderr.expect stderr.actual\n\nSee my earlier worry about this meaning callers still have to check the hookdir.\n\n> +'\n> +\n> +test_expect_success 'git hook run: basic' '\n> +\twrite_script .git/hooks/test-hook <<-EOF &&\n> +\techo Test hook\n> +\tEOF\n> +\n> +\tcat >expect <<-\\EOF &&\n> +\tTest hook\n> +\tEOF\n> +\tgit hook run test-hook 2>actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'git hook run: stdout and stderr both write to our stderr' '\n> +\twrite_script .git/hooks/test-hook <<-EOF &&\n> +\techo >&1 Will end up on stderr\n> +\techo >&2 Will end up on stderr\n> +\tEOF\n> +\n> +\tcat >stderr.expect <<-\\EOF &&\n> +\tWill end up on stderr\n> +\tWill end up on stderr\n> +\tEOF\n> +\tgit hook run test-hook >stdout.actual 2>stderr.actual &&\n> +\ttest_cmp stderr.expect stderr.actual &&\n> +\ttest_must_be_empty stdout.actual\n> +'\n> +\n> +test_expect_success 'git hook run: exit codes are passed along' '\n> +\twrite_script .git/hooks/test-hook <<-EOF &&\n> +\texit 1\n> +\tEOF\n> +\n> +\ttest_expect_code 1 git hook run test-hook &&\n> +\n> +\twrite_script .git/hooks/test-hook <<-EOF &&\n> +\texit 2\n> +\tEOF\n> +\n> +\ttest_expect_code 2 git hook run test-hook &&\n> +\n> +\twrite_script .git/hooks/test-hook <<-EOF &&\n> +\texit 128\n> +\tEOF\n> +\n> +\ttest_expect_code 128 git hook run test-hook &&\n> +\n> +\twrite_script .git/hooks/test-hook <<-EOF &&\n> +\texit 129\n> +\tEOF\n> +\n> +\ttest_expect_code 129 git hook run test-hook\n> +'\n> +\n> +test_expect_success 'git hook run arg u ments without -- is not allowed' '\n> +\ttest_expect_code 129 git hook run test-hook arg u ments\n> +'\n> +\n> +test_expect_success 'git hook run -- pass arguments' '\n> +\twrite_script .git/hooks/test-hook <<-\\EOF &&\n> +\techo $1\n> +\techo $2\n> +\tEOF\n> +\n> +\tcat >expect <<-EOF &&\n> +\targ\n> +\tu ments\n> +\tEOF\n> +\n> +\tgit hook run test-hook -- arg \"u ments\" 2>actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'git hook run -- out-of-repo runs excluded' '\n> +\twrite_script .git/hooks/test-hook <<-EOF &&\n> +\techo Test hook\n> +\tEOF\n> +\n> +\tnongit test_must_fail git hook run test-hook\n> +'\n> +\n> +test_expect_success 'git -c core.hooksPath=<PATH> hook run' '\n> +\tmkdir my-hooks &&\n> +\twrite_script my-hooks/test-hook <<-\\EOF &&\n> +\techo Hook ran $1 >>actual\n> +\tEOF\n> +\n> +\tcat >expect <<-\\EOF &&\n> +\tTest hook\n> +\tHook ran one\n> +\tHook ran two\n> +\tHook ran three\n> +\tHook ran four\n> +\tEOF\n> +\n> +\t# Test various ways of specifying the path. See also\n> +\t# t1350-config-hooks-path.sh\n> +\t>actual &&\n> +\tgit hook run test-hook -- ignored 2>>actual &&\n> +\tgit -c core.hooksPath=my-hooks hook run test-hook -- one 2>>actual &&\n> +\tgit -c core.hooksPath=my-hooks/ hook run test-hook -- two 2>>actual &&\n> +\tgit -c core.hooksPath=\"$PWD/my-hooks\" hook run test-hook -- three 2>>actual &&\n> +\tgit -c core.hooksPath=\"$PWD/my-hooks/\" hook run test-hook -- four 2>>actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_done\n> -- \n> 2.33.0.1567.g7b23ce7ed9e\n\nThanks. Other than my concern about erroring when there is no hook executable,\nit looks fine - but that one is serious enough that I'll withhold the\nReviewed-by line.\n"},{"id":"438867","messageId":"YWmrSAGTwM0Of/oZ@google.com","threadId":"56717","inReplyTo":"patch-v2-02.13-e3dc0aed81b-20211015T093918Z-avarab@gmail.com","subject":"Re: [PATCH v2 02/13] gc: use hook library for pre-auto-gc hook","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2021-10-15T16:24:40Z","receivedAt":"2021-10-15T16:24:49Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Tue, Oct 12, 2021 at 03:30:27PM +0200, Ævar Arnfjörð Bjarmason wrote:\n> \n> \n> Move the pre-auto-gc hook away from run-command.h to and over to the\n> new hook.h library.\n> \n> To do this introduce a simple run_hooks_oneshot() wrapper, we'll be\n> using it extensively for these simple cases of wanting to run a single\n> hook under a given name, and having it free the memory we allocate for\n> us.\n> \n> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> ---\n>  builtin/gc.c |  3 ++-\n>  hook.c       | 20 ++++++++++++++++++++\n>  hook.h       | 13 +++++++++++++\n>  3 files changed, 35 insertions(+), 1 deletion(-)\n> \n> diff --git a/builtin/gc.c b/builtin/gc.c\n> index 6b3de3dd514..95939e77f53 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> @@ -394,7 +395,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_oneshot(\"pre-auto-gc\", NULL))\n\nI like that the oneshot gives us such a simple replacement for run_hooks_le.\n\n>  \t\treturn 0;\n>  \treturn 1;\n>  }\n> diff --git a/hook.c b/hook.c\n> index 240270db64e..bfdc79e2f1a 100644\n> --- a/hook.c\n> +++ b/hook.c\n> @@ -141,3 +141,23 @@ int run_hooks(const char *hook_name, const char *hook_path,\n>  \n>  \treturn cb_data.rc;\n>  }\n> +\n> +int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options)\n> +{\n> +\tconst char *hook_path;\n> +\tstruct run_hooks_opt hook_opt_scratch = RUN_HOOKS_OPT_INIT;\n> +\tint ret = 0;\n> +\n> +\tif (!options)\n> +\t\toptions = &hook_opt_scratch;\n> +\n> +\thook_path = find_hook(hook_name);\n> +\tif (!hook_path)\n> +\t\tgoto cleanup;\n> +\n> +\tret = run_hooks(hook_name, hook_path, options);\n> +cleanup:\n> +\trun_hooks_opt_clear(options);\n> +\n> +\treturn ret;\n> +}\n\nReasonable enough - lookup, run, cleanup. Thanks.\n\n> diff --git a/hook.h b/hook.h\n> index 111c5cf9296..a2b8d4fc6bd 100644\n> --- a/hook.h\n> +++ b/hook.h\n> @@ -49,7 +49,20 @@ void run_hooks_opt_clear(struct run_hooks_opt *o);\n>  /**\n>   * Takes an already resolved hook found via find_hook() and runs\n>   * it. Does not call run_hooks_opt_clear() for you.\n> + *\n> + * See run_hooks_oneshot() for the simpler one-shot API.\n>   */\n>  int run_hooks(const char *hookname, const char *hook_path,\n>  \t      struct run_hooks_opt *options);\n> +\n> +/**\n> + * Calls find_hook() on your \"hook_name\" and runs the hooks (if any)\n> + * with run_hooks().\n> + *\n> + * If \"options\" is provided calls run_hooks_opt_clear() on it for\n> + * you. If \"options\" is NULL the default options from\n> + * RUN_HOOKS_OPT_INIT will be used.\n> + */\n> +int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options);\n> +\n>  #endif\n> -- \n> 2.33.0.1567.g7b23ce7ed9e\n> \n\nReviewed-by: Emily Shaffer <emilyshaffer@google.com>\n"},{"id":"438868","messageId":"YWmrYE65qWjStVzf@google.com","threadId":"56717","inReplyTo":"patch-v2-03.13-6227a1e644d-20211015T093918Z-avarab@gmail.com","subject":"Re: [PATCH v2 03/13] rebase: convert pre-rebase to use hook.h","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2021-10-15T16:25:04Z","receivedAt":"2021-10-15T16:25:23Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Tue, Oct 12, 2021 at 03:30:28PM +0200, Ævar Arnfjörð Bjarmason wrote:\n> \n> \n> Move the pre-rebase hook away from run-command.h to and over to the\n> new hook.h library.\n> \n> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nReviewed-by: Emily Shaffer <emilyshaffer@google.com>\n\n> ---\n>  builtin/rebase.c | 8 ++++++--\n>  1 file changed, 6 insertions(+), 2 deletions(-)\n> \n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index 8c6393f6d78..9a44939d662 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> @@ -1305,6 +1306,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;\n>  \tstruct option builtin_rebase_options[] = {\n>  \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n>  \t\t\t   N_(\"revision\"),\n> @@ -2021,9 +2023,11 @@ 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_push(&hook_opt.args, options.upstream_arg);\n> +\tif (argc)\n> +\t\tstrvec_push(&hook_opt.args, argv[0]);\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_oneshot(\"pre-rebase\", &hook_opt))\n>  \t\tdie(_(\"The pre-rebase hook refused to rebase.\"));\n>  \n>  \tif (options.flags & REBASE_DIFFSTAT) {\n> -- \n> 2.33.0.1567.g7b23ce7ed9e\n> \n"},{"id":"438869","messageId":"YWmrgdFqxOQkjgD7@google.com","threadId":"56717","inReplyTo":"patch-v2-04.13-0e34eb54054-20211015T093918Z-avarab@gmail.com","subject":"Re: [PATCH v2 04/13] am: convert applypatch to use hook.h","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2021-10-15T16:25:37Z","receivedAt":"2021-10-15T16:25:59Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Tue, Oct 12, 2021 at 03:30:29PM +0200, Ævar Arnfjörð Bjarmason wrote:\n> \n> \n> Teach pre-applypatch, post-applypatch, and applypatch-msg to use the\n> hook.h library instead of the run-command.h library.\n> \n> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nReviewed-by: Emily Shaffer <emilyshaffer@google.com>\n\n> ---\n>  builtin/am.c | 8 +++++---\n>  1 file changed, 5 insertions(+), 3 deletions(-)\n> \n> diff --git a/builtin/am.c b/builtin/am.c\n> index 3527945a467..2cfe6451b6f 100644\n> --- a/builtin/am.c\n> +++ b/builtin/am.c\n> @@ -446,9 +446,11 @@ 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;\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_oneshot(\"applypatch-msg\", &opt);\n>  \n>  \tif (!ret) {\n>  \t\tFREE_AND_NULL(state->msg);\n> @@ -1609,7 +1611,7 @@ static void do_commit(const struct am_state *state)\n>  \tconst char *reflog_msg, *author, *committer = NULL;\n>  \tstruct strbuf sb = STRBUF_INIT;\n>  \n> -\tif (run_hook_le(NULL, \"pre-applypatch\", NULL))\n> +\tif (run_hooks_oneshot(\"pre-applypatch\", NULL))\n>  \t\texit(1);\n>  \n>  \tif (write_cache_as_tree(&tree, 0, NULL))\n> @@ -1661,7 +1663,7 @@ 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_oneshot(\"post-applypatch\", NULL);\n>  \n>  \tstrbuf_release(&sb);\n>  }\n> -- \n> 2.33.0.1567.g7b23ce7ed9e\n> \n"},{"id":"438870","messageId":"YWmrl6wMaEnTMsmX@google.com","threadId":"56717","inReplyTo":"patch-v2-05.13-a4df96c1719-20211015T093918Z-avarab@gmail.com","subject":"Re: [PATCH v2 05/13] hooks: convert 'post-checkout' hook to hook library","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2021-10-15T16:25:59Z","receivedAt":"2021-10-15T16:26:10Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Tue, Oct 12, 2021 at 03:30:30PM +0200, Ævar Arnfjörð Bjarmason wrote:\n> \n> \n> Move the running of the 'post-checkout' hook away from run-command.h\n> to the new hook.h library. For \"worktree\" this requires a change to it\n> to run the hooks from a given directory.\n> \n> We could strictly speaking skip the \"absolute_path\" flag and just\n> check if \"dir\" is specified, but let's split them up for clarity, as\n> well as for any future user who'd like to set \"dir\" but not implicitly\n> change the argument to an absolute path.\n> \n> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> --- a/read-cache.c\n> +++ b/read-cache.c\n> @@ -28,6 +28,7 @@\n>  #include \"sparse-index.h\"\n>  #include \"csum-file.h\"\n>  #include \"promisor-remote.h\"\n> +#include \"hook.h\"\n\nHum, is this a stray line? I don't see any other change in read-cache.c?\n\nOtherwise, the change looks reasonable to me.\n"},{"id":"438871","messageId":"YWmrqJetzyi2MbfI@google.com","threadId":"56717","inReplyTo":"patch-v2-06.13-327f916f8c3-20211015T093918Z-avarab@gmail.com","subject":"Re: [PATCH v2 06/13] merge: convert post-merge to use hook.h","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2021-10-15T16:26:16Z","receivedAt":"2021-10-15T16:26:24Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"\nOn Tue, Oct 12, 2021 at 03:30:31PM +0200, Ævar Arnfjörð Bjarmason wrote:\n> \n> \n> Teach post-merge to use the hook.h library instead of the\n> run-command.h library to run hooks.\n> \n> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nReviewed-by: Emily Shaffer <emilyshaffer@google.com>\n"},{"id":"438873","messageId":"YWmruzO9sdYvVIPP@google.com","threadId":"56717","inReplyTo":"patch-v2-07.13-328767015b1-20211015T093918Z-avarab@gmail.com","subject":"Re: [PATCH v2 07/13] git hook run: add an --ignore-missing flag","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2021-10-15T16:26:35Z","receivedAt":"2021-10-15T16:26:45Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"\nOn Tue, Oct 12, 2021 at 03:30:32PM +0200, Ævar Arnfjörð Bjarmason wrote:\n> \n> For certain one-shot hooks we'd like to optimistically run them, and\n> not complain if they don't exist. This will be used by send-email in a\n> subsequent commit.\n\nAha, this is what I was looking for in the earlier commit. Feel free to\ndisregard those complaints. ;)\n\n> \n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nReviewed-by: Emily Shaffer <emilyshaffer@google.com>\n"},{"id":"438878","messageId":"YWm1ZdLJqQG8ywiH@google.com","threadId":"56717","inReplyTo":"patch-v2-08.13-6c4ebd68d56-20211015T093918Z-avarab@gmail.com","subject":"Re: [PATCH v2 08/13] send-email: use 'git hook run' for 'sendemail-validate'","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2021-10-15T17:07:49Z","receivedAt":"2021-10-15T17:07:57Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Fri, Oct 15, 2021 at 11:43:36AM +0200, Ævar Arnfjörð Bjarmason wrote:\n> \n> \n> Change the \"sendmail-validate\" hook to be run via the \"git hook run\"\n> wrapper instead of via a direct invocation.\n> \n> This is the smallest possibly change to get \"send-email\" using \"git\n> hook run\". We still check the hook itself with \"-x\", and set a\n> \"GIT_DIR\" variable, both of which are asserted by our tests. We'll\n> need to get rid of this special behavior if we start running N hooks,\n> but for now let's be as close to bug-for-bug compatible as possible.\n> \n> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nSomeday we can delete a bunch more in there and on that day we can\ncelebrate. But the rationale for making the change minimal LGTM.\n\nReviewed-by: Emily Shaffer <emilyshaffer@google.com>\n\n> ---\n>  git-send-email.perl   | 22 ++++++++++++++--------\n>  t/t9001-send-email.sh |  4 ++--\n>  2 files changed, 16 insertions(+), 10 deletions(-)\n> \n> diff --git a/git-send-email.perl b/git-send-email.perl\n> index 5262d88ee32..4c20c0bbb79 100755\n> --- a/git-send-email.perl\n> +++ b/git-send-email.perl\n> @@ -195,13 +195,13 @@ sub format_2822_time {\n>  my $editor;\n>  \n>  sub system_or_msg {\n> -\tmy ($args, $msg) = @_;\n> +\tmy ($args, $msg, $cmd_name) = @_;\n>  \tsystem(@$args);\n>  \tmy $signalled = $? & 127;\n>  \tmy $exit_code = $? >> 8;\n>  \treturn unless $signalled or $exit_code;\n>  \n> -\tmy @sprintf_args = ($args->[0], $exit_code);\n> +\tmy @sprintf_args = ($cmd_name ? $cmd_name : $args->[0], $exit_code);\n>  \tif (defined $msg) {\n>  \t\t# Quiet the 'redundant' warning category, except we\n>  \t\t# need to support down to Perl 5.8, so we can't do a\n> @@ -2039,10 +2039,10 @@ sub validate_patch {\n>  \tmy ($fn, $xfer_encoding) = @_;\n>  \n>  \tif ($repo) {\n> +\t\tmy $hook_name = 'sendemail-validate';\n>  \t\tmy $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks');\n>  \t\trequire File::Spec;\n> -\t\tmy $validate_hook = File::Spec->catfile($hooks_path,\n> -\t\t\t\t\t    'sendemail-validate');\n> +\t\tmy $validate_hook = File::Spec->catfile($hooks_path, $hook_name);\n>  \t\tmy $hook_error;\n>  \t\tif (-x $validate_hook) {\n>  \t\t\trequire Cwd;\n> @@ -2052,13 +2052,19 @@ sub validate_patch {\n>  \t\t\tchdir($repo->wc_path() or $repo->repo_path())\n>  \t\t\t\tor die(\"chdir: $!\");\n>  \t\t\tlocal $ENV{\"GIT_DIR\"} = $repo->repo_path();\n> -\t\t\t$hook_error = system_or_msg([$validate_hook, $target]);\n> +\t\t\tmy @cmd = (\"git\", \"hook\", \"run\", \"--ignore-missing\",\n> +\t\t\t\t    $hook_name, \"--\");\n> +\t\t\tmy @cmd_msg = (@cmd, \"<patch>\");\n> +\t\t\tmy @cmd_run = (@cmd, $target);\n> +\t\t\t$hook_error = system_or_msg(\\@cmd_run, undef, \"@cmd_msg\");\n>  \t\t\tchdir($cwd_save) or die(\"chdir: $!\");\n>  \t\t}\n>  \t\tif ($hook_error) {\n> -\t\t\tdie sprintf(__(\"fatal: %s: rejected by sendemail-validate hook\\n\" .\n> -\t\t\t\t       \"%s\\n\" .\n> -\t\t\t\t       \"warning: no patches were sent\\n\"), $fn, $hook_error);\n> +\t\t\t$hook_error = sprintf(__(\"fatal: %s: rejected by %s hook\\n\" .\n> +\t\t\t\t\t\t $hook_error . \"\\n\" .\n> +\t\t\t\t\t\t \"warning: no patches were sent\\n\"),\n> +\t\t\t\t\t      $fn, $hook_name);\n> +\t\t\tdie $hook_error;\n>  \t\t}\n>  \t}\n>  \n> diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\n> index aa0c20499ba..84d0f40d76a 100755\n> --- a/t/t9001-send-email.sh\n> +++ b/t/t9001-send-email.sh\n> @@ -539,7 +539,7 @@ test_expect_success $PREREQ \"--validate respects relative core.hooksPath path\" '\n>  \ttest_path_is_file my-hooks.ran &&\n>  \tcat >expect <<-EOF &&\n>  \tfatal: longline.patch: rejected by sendemail-validate hook\n> -\tfatal: command '\"'\"'my-hooks/sendemail-validate'\"'\"' died with exit code 1\n> +\tfatal: command '\"'\"'git hook run --ignore-missing sendemail-validate -- <patch>'\"'\"' died with exit code 1\n>  \twarning: no patches were sent\n>  \tEOF\n>  \ttest_cmp expect actual\n> @@ -558,7 +558,7 @@ test_expect_success $PREREQ \"--validate respects absolute core.hooksPath path\" '\n>  \ttest_path_is_file my-hooks.ran &&\n>  \tcat >expect <<-EOF &&\n>  \tfatal: longline.patch: rejected by sendemail-validate hook\n> -\tfatal: command '\"'\"'$hooks_path/sendemail-validate'\"'\"' died with exit code 1\n> +\tfatal: command '\"'\"'git hook run --ignore-missing sendemail-validate -- <patch>'\"'\"' died with exit code 1\n>  \twarning: no patches were sent\n>  \tEOF\n>  \ttest_cmp expect actual\n> -- \n> 2.33.1.1338.g20da966911a\n> \n"},{"id":"438879","messageId":"YWm1mksCaxJmRoFR@google.com","threadId":"56717","inReplyTo":"patch-v2-09.13-b1f52733e3c-20211015T093918Z-avarab@gmail.com","subject":"Re: [PATCH v2 09/13] git-p4: use 'git hook' to run hooks","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2021-10-15T17:08:42Z","receivedAt":"2021-10-15T17:08:51Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Fri, Oct 15, 2021 at 11:43:37AM +0200, Ævar Arnfjörð Bjarmason wrote:\n> \n> \n> Instead of duplicating the behavior of run-command.h:run_hook_le() in\n> Python, we can directly call 'git hook run'. We emulate the existence\n> check with the --ignore-missing flag.\n> \n> As this is the last hook execution in git.git to not go through \"git\n> hook run\" or the hook.[ch] library we can now be absolutely sure that\n> our assertion in hook.c that only hooks known by the generated (from\n> githooks(5)) hook-list.h are permitted.\n> \n> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> ---\n>  git-p4.py | 72 ++++++-------------------------------------------------\n>  1 file changed, 7 insertions(+), 65 deletions(-)\n> \n> diff --git a/git-p4.py b/git-p4.py\n> index 2b4500226aa..1f24cbf0bca 100755\n> --- a/git-p4.py\n> +++ b/git-p4.py\n> @@ -207,71 +207,13 @@ def decode_path(path):\n>          return 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> -        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> -\n> +    \"\"\"args are specified with -a <arg> -a <arg> -a <arg>\"\"\"\n\nAh, the comment is misleading now, since we're adding args after the --.\n\n> +    args = ['git', 'hook', 'run', '--ignore-missing', cmd]\n> +    if param:\n> +        args.append(\"--\")\n> +        for p in param:\n> +            args.append(p)\n> +    return subprocess.call(args) == 0\n>  \n>  def write_pipe(c, stdin):\n>      if verbose:\n> -- \n> 2.33.1.1338.g20da966911a\n> \n"},{"id":"438881","messageId":"YWm3P5EqiE4hYrJr@google.com","threadId":"56717","inReplyTo":"patch-v2-10.13-dc31d98acdf-20211015T093918Z-avarab@gmail.com","subject":"Re: [PATCH v2 10/13] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2021-10-15T17:15:43Z","receivedAt":"2021-10-15T17:15:57Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Fri, Oct 15, 2021 at 11:43:38AM +0200, Ævar Arnfjörð Bjarmason wrote:\n> \n> \n> Move these hooks hook away from run-command.h to and over to the new\n> hook.h library.\n> \n> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nVery straightforward.\nReviewed-by: Emily Shaffer <emilyshaffer@google.com>\n\n> ---\n>  commit.c | 15 ++++++++-------\n>  1 file changed, 8 insertions(+), 7 deletions(-)\n> \n> diff --git a/commit.c b/commit.c\n> index 551de4903c1..4e7cbd7585d 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> @@ -1700,22 +1701,22 @@ size_t ignore_non_trailer(const char *buf, size_t len)\n>  int run_commit_hook(int editor_is_used, 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;\n>  \tva_list args;\n> -\tint ret;\n> +\tconst char *arg;\n>  \n> -\tstrvec_pushf(&hook_env, \"GIT_INDEX_FILE=%s\", index_file);\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> -\treturn ret;\n> +\treturn run_hooks_oneshot(name, &opt);\n>  }\n> -- \n> 2.33.1.1338.g20da966911a\n> \n"},{"id":"438882","messageId":"YWm3wF4GOWAua2MZ@google.com","threadId":"56717","inReplyTo":"patch-v2-11.13-58b7689e4af-20211015T093918Z-avarab@gmail.com","subject":"Re: [PATCH v2 11/13] read-cache: convert post-index-change to use hook.h","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2021-10-15T17:17:52Z","receivedAt":"2021-10-15T17:18:00Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Fri, Oct 15, 2021 at 11:43:39AM +0200, Ævar Arnfjörð Bjarmason wrote:\n> \n> \n> Move the post-index-change hook away from run-command.h to and over to\n> the new hook.h library.\n> \n> This removes the last direct user of \"run_hook_ve()\" outside of\n> run-command.c (\"run_hook_le()\" still uses it). So we can make the\n> function static now. A subsequent commit will remove this code\n> entirely when \"run_hook_le()\" itself goes away.\n\n\\o/\n\n> \n> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nReviewed-by: Emily Shaffer <emilyshaffer@google.com>\n\n> ---\n>  read-cache.c  | 10 +++++++---\n>  run-command.c |  2 +-\n>  run-command.h |  1 -\n>  3 files changed, 8 insertions(+), 5 deletions(-)\n> \n> diff --git a/read-cache.c b/read-cache.c\n> index 9773118d078..84cc3d88196 100644\n> --- a/read-cache.c\n> +++ b/read-cache.c\n> @@ -3087,6 +3087,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n>  {\n>  \tint ret;\n>  \tint was_full = !istate->sparse_index;\n> +\tstruct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;\n>  \n>  \tret = convert_to_sparse(istate, 0);\n>  \n> @@ -3115,9 +3116,12 @@ 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_oneshot(\"post-index-change\", &hook_opt);\n> +\n>  \tistate->updated_workdir = 0;\n>  \tistate->updated_skipworktree = 0;\n>  \n> diff --git a/run-command.c b/run-command.c\n> index 7ef5cc712a9..d92e670c8ed 100644\n> --- a/run-command.c\n> +++ b/run-command.c\n> @@ -1323,7 +1323,7 @@ int async_with_fork(void)\n>  #endif\n>  }\n>  \n> -int run_hook_ve(const char *const *env, const char *name, va_list args)\n> +static 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> diff --git a/run-command.h b/run-command.h\n> index 49878262584..3fa7454cf8a 100644\n> --- a/run-command.h\n> +++ b/run-command.h\n> @@ -239,7 +239,6 @@ int run_command(struct child_process *);\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> 2.33.1.1338.g20da966911a\n> \n"},{"id":"438883","messageId":"YWm37gqAyxyc5X07@google.com","threadId":"56717","inReplyTo":"patch-v2-13.13-289d5a2d849-20211015T093918Z-avarab@gmail.com","subject":"Re: [PATCH v2 13/13] run-command: remove old run_hook_{le,ve}() hook API","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2021-10-15T17:18:38Z","receivedAt":"2021-10-15T17:18:47Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Fri, Oct 15, 2021 at 11:43:41AM +0200, Ævar Arnfjörð Bjarmason wrote:\n> \n> \n> The new hook.h library has replaced all run-command.h hook-related\n> functionality. So let's delete this dead code.\n> \n> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nReviewed-by: Emily Shaffer <emilyshaffer@google.com>\n\nThanks.\n\n> ---\n>  run-command.c | 32 --------------------------------\n>  run-command.h | 16 ----------------\n>  2 files changed, 48 deletions(-)\n> \n> diff --git a/run-command.c b/run-command.c\n> index d92e670c8ed..8a21ff525f3 100644\n> --- a/run-command.c\n> +++ b/run-command.c\n> @@ -1323,38 +1323,6 @@ int async_with_fork(void)\n>  #endif\n>  }\n>  \n> -static 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;\n> diff --git a/run-command.h b/run-command.h\n> index 3fa7454cf8a..59e1fbff64c 100644\n> --- a/run-command.h\n> +++ b/run-command.h\n> @@ -224,22 +224,6 @@ int finish_command_in_signal(struct child_process *);\n>   */\n>  int run_command(struct child_process *);\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> -\n>  /*\n>   * Trigger an auto-gc\n>   */\n> -- \n> 2.33.1.1338.g20da966911a\n> \n"},{"id":"438886","messageId":"CAPig+cR3=K0HMtqW5smBOHKNksY3yqYy1tTPLbEyuFmULPsCBg@mail.gmail.com","threadId":"56717","inReplyTo":"YWmrL/POUE8P8Od1@google.com","subject":"Re: [PATCH v2 01/13] hook: add 'run' subcommand","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2021-10-15T17:53:03Z","receivedAt":"2021-10-15T17:53:16Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Oct 15, 2021 at 12:24 PM Emily Shaffer <emilyshaffer@google.com> wrote:\n> On Tue, Oct 12, 2021 at 03:30:26PM +0200, Ævar Arnfjörð Bjarmason wrote:\n> > +     if (argc > 1 && strcmp(argv[1], \"--\") &&\n> > +         strcmp(argv[1], \"--end-of-options\"))\n>\n> --end-of-options isn't documented anywhere, right? Fine, I don't mind, but it's\n> interesting to support it \"secretly\".\n\nThis option is documented in Documentation/gitcli.txt (i.e. `git help cli`).\n"},{"id":"438917","messageId":"xmqqbl3p4wgz.fsf@gitster.g","threadId":"56717","inReplyTo":"cover-v2-00.13-00000000000-20211015T093918Z-avarab@gmail.com","subject":"Re: [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-16T00:58:04Z","receivedAt":"2021-10-16T00:58:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:\n\n> Part 2 of the greater configurable hook saga, starting by converting\n> some existing simple hooks to the new hook.[ch] library and \"git hook\n> run\" utility.\n>\n> See the v1 [1] CL for more context. I've dropped the git-p4 folks from\n> the CC list, your feedback on git-p4 would still be very appreciated,\n> but I don't think I need to re-spam you for every re-roll of this.\n\n\nThis, when merged to 'seen', seems to fail so many tests to make me\nquestion my sanity.  Something is not playing with it well.\n\nQueued on top of a merge of ab/config-based-hooks-1 into 'master';\nwhen tested alone, without any other topics in 'seen', it seems to\nfare much better.  Only t2400-worktree-add.sh fails at 58-59 and 62.\n\n\n\nTest Summary Report\n-------------------\nt1092-sparse-checkout-compatibility.sh           (Wstat: 256 Tests: 36 Failed: 2)\n  Failed tests:  23, 25\n  Non-zero exit status: 1\nt5572-pull-submodule.sh                          (Wstat: 256 Tests: 64 Failed: 2)\n  Failed tests:  61, 63\n  Non-zero exit status: 1\nt3432-rebase-fast-forward.sh                     (Wstat: 256 Tests: 225 Failed: 203)\n  Failed tests:  15-85, 87-170, 172-201, 203, 205, 207, 209\n                211, 213-225\n  Non-zero exit status: 1\nt7406-submodule-update.sh                        (Wstat: 256 Tests: 58 Failed: 9)\n  Failed tests:  14-22\n  Non-zero exit status: 1\nt3404-rebase-interactive.sh                      (Wstat: 256 Tests: 119 Failed: 93)\n  Failed tests:  4-6, 8-9, 14-21, 24-42, 44-47, 49, 52-59\n                61, 63-69, 71-78, 80-86, 89, 92-104, 106\n                108, 110-118\n  Non-zero exit status: 1\nt3426-rebase-submodule.sh                        (Wstat: 256 Tests: 29 Failed: 18)\n  Failed tests:  1-6, 11-13, 15-20, 25-27\n  Non-zero exit status: 1\nt0021-conversion.sh                              (Wstat: 256 Tests: 41 Failed: 1)\n  Failed test:  18\n  Non-zero exit status: 1\nt5520-pull.sh                                    (Wstat: 256 Tests: 70 Failed: 26)\n  Failed tests:  19, 23-24, 26-29, 40-41, 43, 45-46, 49-52\n                55, 57-62, 67, 69-70\n  Non-zero exit status: 1\nt5533-push-cas.sh                                (Wstat: 256 Tests: 23 Failed: 2)\n  Failed tests:  21-22\n  Non-zero exit status: 1\nt3200-branch.sh                                  (Wstat: 256 Tests: 145 Failed: 2)\n  Failed tests:  140-141\n  Non-zero exit status: 1\nt3421-rebase-topology-linear.sh                  (Wstat: 256 Tests: 64 Failed: 50)\n  Failed tests:  2-4, 9-16, 22-27, 29-33, 36-42, 44-64\n  Non-zero exit status: 1\nt2400-worktree-add.sh                            (Wstat: 256 Tests: 71 Failed: 6)\n  Failed tests:  41, 45-46, 58-59, 62\n  Non-zero exit status: 1\nt7512-status-help.sh                             (Wstat: 256 Tests: 44 Failed: 20)\n  Failed tests:  5-6, 8-12, 14-22, 28, 41-43\n  Non-zero exit status: 1\nt3420-rebase-autostash.sh                        (Wstat: 256 Tests: 42 Failed: 38)\n  Failed tests:  2-3, 5-36, 39-42\n  Non-zero exit status: 1\nt7601-merge-pull-config.sh                       (Wstat: 256 Tests: 62 Failed: 11)\n  Failed tests:  15, 25-26, 28-29, 33-36, 42-43\n  Non-zero exit status: 1\nt3430-rebase-merges.sh                           (Wstat: 256 Tests: 25 Failed: 21)\n  Failed tests:  2, 5-10, 12-25\n  Non-zero exit status: 1\nt9903-bash-prompt.sh                             (Wstat: 256 Tests: 66 Failed: 47)\n  Failed tests:  14-35, 37, 40-52, 54-63, 66\n  Non-zero exit status: 1\nt3415-rebase-autosquash.sh                       (Wstat: 256 Tests: 24 Failed: 23)\n  Failed tests:  2-24\n  Non-zero exit status: 1\nt3418-rebase-continue.sh                         (Wstat: 256 Tests: 29 Failed: 19)\n  Failed tests:  2-3, 6-9, 11-19, 26-29\n  Non-zero exit status: 1\nt3433-rebase-across-mode-change.sh               (Wstat: 256 Tests: 4 Failed: 3)\n  Failed tests:  2-4\n  Non-zero exit status: 1\nt3400-rebase.sh                                  (Wstat: 256 Tests: 35 Failed: 23)\n  Failed tests:  4-6, 10-13, 18-33\n  Non-zero exit status: 1\nt7528-signed-commit-ssh.sh                       (Wstat: 256 Tests: 23 Failed: 14)\n  Failed tests:  1-5, 7, 10-17\n  Non-zero exit status: 1\nt3435-rebase-gpg-sign.sh                         (Wstat: 256 Tests: 20 Failed: 17)\n  Failed tests:  3-11, 13-20\n  Non-zero exit status: 1\nt7510-signed-commit.sh                           (Wstat: 256 Tests: 25 Failed: 19)\n  Failed tests:  1-6, 8, 11-19, 23-25\n  Non-zero exit status: 1\nt3431-rebase-fork-point.sh                       (Wstat: 256 Tests: 26 Failed: 23)\n  Failed tests:  2-19, 22-26\n  Non-zero exit status: 1\nt5407-post-rewrite-hook.sh                       (Wstat: 256 Tests: 16 Failed: 13)\n  Failed tests:  4-16\n  Non-zero exit status: 1\nt7505-prepare-commit-msg-hook.sh                 (Wstat: 256 Tests: 20 Failed: 1)\n  Failed test:  14\n  Non-zero exit status: 1\nt3901-i18n-patch.sh                              (Wstat: 256 Tests: 20 Failed: 8)\n  Failed tests:  4-7, 12-15\n  Non-zero exit status: 1\nt3900-i18n-commit.sh                             (Wstat: 256 Tests: 38 Failed: 6)\n  Failed tests:  33-38\n  Non-zero exit status: 1\nt2012-checkout-last.sh                           (Wstat: 256 Tests: 22 Failed: 4)\n  Failed tests:  19-22\n  Non-zero exit status: 1\nt3436-rebase-more-options.sh                     (Wstat: 256 Tests: 17 Failed: 15)\n  Failed tests:  2-16\n  Non-zero exit status: 1\nt3437-rebase-fixup-options.sh                    (Wstat: 256 Tests: 11 Failed: 10)\n  Failed tests:  2-11\n  Non-zero exit status: 1\nt3407-rebase-abort.sh                            (Wstat: 256 Tests: 15 Failed: 5)\n  Failed tests:  3, 5, 10-12\n  Non-zero exit status: 1\nt7504-commit-msg-hook.sh                         (Wstat: 256 Tests: 25 Failed: 1)\n  Failed test:  25\n  Non-zero exit status: 1\nt7030-verify-tag.sh                              (Wstat: 256 Tests: 15 Failed: 6)\n  Failed tests:  1, 3, 8-9, 11-12\n  Non-zero exit status: 1\nt3402-rebase-merge.sh                            (Wstat: 256 Tests: 14 Failed: 12)\n  Failed tests:  3-14\n  Non-zero exit status: 1\nt3403-rebase-skip.sh                             (Wstat: 256 Tests: 20 Failed: 12)\n  Failed tests:  4-5, 8, 11-19\n  Non-zero exit status: 1\nt3412-rebase-root.sh                             (Wstat: 256 Tests: 25 Failed: 12)\n  Failed tests:  4, 6-8, 10-13, 19, 21-22, 24\n  Non-zero exit status: 1\nt3425-rebase-topology-merges.sh                  (Wstat: 256 Tests: 13 Failed: 12)\n  Failed tests:  2-13\n  Non-zero exit status: 1\nt7031-verify-tag-signed-ssh.sh                   (Wstat: 256 Tests: 8 Failed: 6)\n  Failed tests:  1-6\n  Non-zero exit status: 1\nt3424-rebase-empty.sh                            (Wstat: 256 Tests: 15 Failed: 13)\n  Failed tests:  3-15\n  Non-zero exit status: 1\nt7517-per-repo-email.sh                          (Wstat: 256 Tests: 16 Failed: 3)\n  Failed tests:  7, 9, 11\n  Non-zero exit status: 1\nt6427-diff3-conflict-markers.sh                  (Wstat: 256 Tests: 9 Failed: 2)\n  Failed tests:  7-8\n  Non-zero exit status: 1\nt3416-rebase-onto-threedots.sh                   (Wstat: 256 Tests: 13 Failed: 6)\n  Failed tests:  2-3, 5-6, 10, 12\n  Non-zero exit status: 1\nt3406-rebase-message.sh                          (Wstat: 256 Tests: 14 Failed: 13)\n  Failed tests:  2-14\n  Non-zero exit status: 1\nt3413-rebase-hook.sh                             (Wstat: 256 Tests: 15 Failed: 10)\n  Failed tests:  2-3, 5-10, 14-15\n  Non-zero exit status: 1\nt3401-rebase-and-am-rename.sh                    (Wstat: 256 Tests: 10 Failed: 5)\n  Failed tests:  2, 4, 7-9\n  Non-zero exit status: 1\nt3434-rebase-i18n.sh                             (Wstat: 256 Tests: 6 Failed: 5)\n  Failed tests:  2-6\n  Non-zero exit status: 1\nt7402-submodule-rebase.sh                        (Wstat: 256 Tests: 6 Failed: 3)\n  Failed tests:  2-3, 6\n  Non-zero exit status: 1\nt3429-rebase-edit-todo.sh                        (Wstat: 256 Tests: 6 Failed: 2)\n  Failed tests:  3-4\n  Non-zero exit status: 1\nt3419-rebase-patch-id.sh                         (Wstat: 256 Tests: 5 Failed: 1)\n  Failed test:  4\n  Non-zero exit status: 1\nt3428-rebase-signoff.sh                          (Wstat: 256 Tests: 6 Failed: 6)\n  Failed tests:  1-6\n  Non-zero exit status: 1\nt5403-post-checkout-hook.sh                      (Wstat: 256 Tests: 8 Failed: 2)\n  Failed tests:  6-7\n  Non-zero exit status: 1\nt3427-rebase-subtree.sh                          (Wstat: 256 Tests: 3 Failed: 2)\n  Failed tests:  2-3\n  Non-zero exit status: 1\nt3423-rebase-reword.sh                           (Wstat: 256 Tests: 3 Failed: 2)\n  Failed tests:  2-3\n  Non-zero exit status: 1\nt3417-rebase-whitespace-fix.sh                   (Wstat: 256 Tests: 4 Failed: 4)\n  Failed tests:  1-4\n  Non-zero exit status: 1\nt3405-rebase-malformed.sh                        (Wstat: 256 Tests: 5 Failed: 3)\n  Failed tests:  3-5\n  Non-zero exit status: 1\nt3408-rebase-multi-line.sh                       (Wstat: 256 Tests: 2 Failed: 1)\n  Failed test:  2\n  Non-zero exit status: 1\nFiles=942, Tests=24498, 113 wallclock secs ( 8.74 usr  2.04 sys + 726.97 cusr 424.82 csys = 1162.57 CPU)\nResult: FAIL\ng\n"},{"id":"438922","messageId":"CAPig+cSUFE7zXo25iSpwYA7QAine+-6xfCVBSb3DupdyoDw1WQ@mail.gmail.com","threadId":"56717","inReplyTo":"xmqqbl3p4wgz.fsf@gitster.g","subject":"Re: [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2021-10-16T05:22:31Z","receivedAt":"2021-10-16T05:22:45Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Oct 15, 2021 at 8:58 PM Junio C Hamano <gitster@pobox.com> wrote:\n> Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:\n> > Part 2 of the greater configurable hook saga, starting by converting\n> > some existing simple hooks to the new hook.[ch] library and \"git hook\n> > run\" utility.\n>\n> This, when merged to 'seen', seems to fail so many tests to make me\n> question my sanity.  Something is not playing with it well.\n>\n> Queued on top of a merge of ab/config-based-hooks-1 into 'master';\n> when tested alone, without any other topics in 'seen', it seems to\n> fare much better.  Only t2400-worktree-add.sh fails at 58-59 and 62.\n\nFor what it's worth, `git worktree add` is special in that it has to\nrun the `post-checkout` hook in the newly-created worktree, not in the\nworktree in which the `git worktree add` command was issued.\n"},{"id":"438923","messageId":"874k9hecro.fsf@evledraar.gmail.com","threadId":"56717","inReplyTo":"xmqqbl3p4wgz.fsf@gitster.g","subject":"Re: [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-16T05:47:04Z","receivedAt":"2021-10-16T05:53:36Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Oct 15 2021, Junio C Hamano wrote:\n\n> Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:\n>\n>> Part 2 of the greater configurable hook saga, starting by converting\n>> some existing simple hooks to the new hook.[ch] library and \"git hook\n>> run\" utility.\n>>\n>> See the v1 [1] CL for more context. I've dropped the git-p4 folks from\n>> the CC list, your feedback on git-p4 would still be very appreciated,\n>> but I don't think I need to re-spam you for every re-roll of this.\n>\n>\n> This, when merged to 'seen', seems to fail so many tests to make me\n> question my sanity.  Something is not playing with it well.\n>\n> Queued on top of a merge of ab/config-based-hooks-1 into 'master';\n> when tested alone, without any other topics in 'seen', it seems to\n> fare much better.  Only t2400-worktree-add.sh fails at 58-59 and 62.\n\nI haven't looked into worktree-add.sh failure (and from what Eric says\nit seems unrelated), but all the rest is due to a mismerge of\nreset.c. The diff here at the end on top of \"seen\" fixes it[1].\n\nI'm able to reproduce it with:\n\n    $ git reference 978c287ca92 # just a \"show\" alias...\n    978c287ca92 (hooks: convert 'post-checkout' hook to hook library, 2021-10-15)\n    $ git checkout origin/seen^1; git -c rerere.enabled=false cherry-pick 978c287ca92\n\nWhich has a big conflict, including:\n    \n    @@ -158,10 -90,54 +159,58 @@@\n                    goto leave_reset_head;\n            }\n      \n    ++<<<<<<< HEAD\n     +      if (oid != &head_oid || update_orig_head || switch_to_branch)\n     +              ret = update_refs(opts, oid);\n    ++=======\n    + reset_head_refs:\n    +       reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);\n    +       strbuf_addf(&msg, \"%s: \", reflog_action ? reflog_action : default_reflog_action);\n\nI.e. the oid check there went away, but needs to be kept as well.\n\n1.\n\ndiff --git a/reset.c b/reset.c\nindex 6441b22eebc..602cba75c1c 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -165,6 +165,8 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts)\n                goto leave_reset_head;\n        }\n \n+       if (oid != &head_oid || update_orig_head || switch_to_branch)\n+               ret = update_refs(opts, oid);\n \n leave_reset_head:\n        rollback_lock_file(&lock);\n"},{"id":"438949","messageId":"xmqqwnmc3kz0.fsf@gitster.g","threadId":"56717","inReplyTo":"874k9hecro.fsf@evledraar.gmail.com","subject":"Re: [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-16T18:04:03Z","receivedAt":"2021-10-16T18:04:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> I haven't looked into worktree-add.sh failure (and from what Eric says\n> it seems unrelated), but all the rest is due to a mismerge of\n> reset.c. The diff here at the end on top of \"seen\" fixes it[1].\n\nAhh, of course.\n\nThe \"update_refs()\" call is what replaces the\nbig block that had a bit of conflict in the way how a hook is run,\nwhere the original made a call to run_hook_le() now we set up an\nopt structure and make a call to run_hooks_oneshot().  Dropping the\ncall to it would of course mean no hooks would be run from there X-<.\n\nThanks.\n"},{"id":"438950","messageId":"87v91wdelt.fsf@evledraar.gmail.com","threadId":"56717","inReplyTo":"xmqqwnmc3kz0.fsf@gitster.g","subject":"Re: [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-16T18:10:07Z","receivedAt":"2021-10-16T18:11:33Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sat, Oct 16 2021, Junio C Hamano wrote:\n\n> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n>\n>> I haven't looked into worktree-add.sh failure (and from what Eric says\n>> it seems unrelated), but all the rest is due to a mismerge of\n>> reset.c. The diff here at the end on top of \"seen\" fixes it[1].\n>\n> Ahh, of course.\n>\n> The \"update_refs()\" call is what replaces the\n> big block that had a bit of conflict in the way how a hook is run,\n> where the original made a call to run_hook_le() now we set up an\n> opt structure and make a call to run_hooks_oneshot().  Dropping the\n> call to it would of course mean no hooks would be run from there X-<.\n>\n> Thanks.\n\nI was thinking of re-rolling that series soon, hopefully with \"last\nnits\", would you prefer to just have the reset.c part of it ejected for\nnow?\n\nIt's really nothing that needs to happen right now, we can do it in some\nsubsequent round when activity in that area has hopefully quieted.\n\nIt also means dropping the \"remove old run_hook_{le,ve}() hook API\", but\nthat can similarly come later...\n"},{"id":"439056","messageId":"211020.86mtn4obhr.gmgdl@evledraar","threadId":"56717","inReplyTo":"YWmrL/POUE8P8Od1@google.com","subject":"Re: [PATCH v2 01/13] hook: add 'run' subcommand","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-19T23:08:14Z","receivedAt":"2021-10-19T23:12:25Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Oct 15 2021, Emily Shaffer wrote:\n\n> On Tue, Oct 12, 2021 at 03:30:26PM +0200, Ævar Arnfjörð Bjarmason wrote:\n>> \n>> \n>> In order to enable hooks to be run as an external process, by a\n>> standalone Git command, or by tools which wrap Git, provide an external\n>> means to run all configured hook commands for a given hook event.\n>> \n>> Most of our hooks require more complex functionality than this, but\n>> let's start with the bare minimum required to support our simplest\n>> hooks.\n>> \n>> In terms of implementation the usage_with_options() and \"goto usage\"\n>> pattern here mirrors that of\n>> builtin/{commit-graph,multi-pack-index}.c.\n>> \n>> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>\n>> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n>> ---\n>> diff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt\n>> new file mode 100644\n>> index 00000000000..660d6a992a0\n>> --- /dev/null\n>> +++ b/Documentation/git-hook.txt\n>> @@ -0,0 +1,38 @@\n>> +git-hook(1)\n>> +===========\n>> +\n>> +NAME\n>> +----\n>> +git-hook - run git hooks\n>> +\n>> +SYNOPSIS\n>> +--------\n>> +[verse]\n>> +'git hook' run <hook-name> [-- <hook-args>]\n>> +\n>> +DESCRIPTION\n>> +-----------\n>> +\n>> +This command is an interface to git hooks (see linkgit:githooks[5]).\n>> +Currently it only provides a convenience wrapper for running hooks for\n>> +use by git itself. In the future it might gain other functionality.\n>\n> s/git/Git/g nit, no? I thought we were somewhat picky about capitalizing Git\n> when we don't mean `git`?\n\nI'm not sure how this got mixed up, but the text you've got quoted here\nis from the v1 at:\nhttps://lore.kernel.org/git/patch-01.13-a39c0748d3f-20211012T131934Z-avarab@gmail.com/\n\nBut you're replying to the v2 here, i.e. this, which has no such text:\nhttps://lore.kernel.org/git/patch-v2-01.13-ba64faf0580-20211015T093918Z-avarab@gmail.com/\n\nI see I managed to omit the In-Reply-To link from v2 to v1 by mistake,\nmaybe some funny mailer interaction with that?\n\nAnyway for all the rest of the comments below & in this thread I tried\nto address them in some way, re-rolling soon...\n"},{"id":"439058","messageId":"cover-v3-00.13-00000000000-20211019T231647Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v2-00.13-00000000000-20211015T093918Z-avarab@gmail.com","subject":"[PATCH v3 00/13] hook.[ch]: new library to run hooks + simple hook conversion","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-19T23:20:38Z","receivedAt":"2021-10-19T23:21:29Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Part 2 of the greater configurable hook saga, starting by converting\nsome existing simple hooks to the new hook.[ch] library and \"git hook\nrun\" utility.\n\nFor more context:\n\nSee v2: https://lore.kernel.org/git/cover-v2-00.13-00000000000-20211015T093918Z-avarab@gmail.com\nand v1: https://lore.kernel.org/git/cover-00.13-00000000000-20211012T131934Z-avarab@gmail.com/\n\nChanges since v2:\n\n * I got some of the s/struct hook/const char *hook_path/ conversion\n   wrong in v1->v2, which is the cause of the bug in \"seen\" with\n   t2400*.sh, this fixes that.\n\n * The commit message for the git-p4 change was also out of date (made\n   sense before some previous re-ordering). I also updated a comment\n   there.\n\n * Rebased on \"master\" for getting past a couple of small\n   conflicts. (seen in the range-diff).\n\n * An include was happening in the wrong commit (was used later in the\n   series), moved.\n\nEmily Shaffer (12):\n  hook: add 'run' subcommand\n  gc: use hook library for pre-auto-gc hook\n  rebase: convert pre-rebase to use hook.h\n  am: convert applypatch to use hook.h\n  hooks: convert 'post-checkout' hook to hook library\n  merge: convert post-merge to use hook.h\n  send-email: use 'git hook run' for 'sendemail-validate'\n  git-p4: use 'git hook' to run hooks\n  commit: convert {pre-commit,prepare-commit-msg} hook to hook.h\n  read-cache: convert post-index-change to use hook.h\n  receive-pack: convert push-to-checkout hook to hook.h\n  run-command: remove old run_hook_{le,ve}() hook API\n\nÆvar Arnfjörð Bjarmason (1):\n  git hook run: add an --ignore-missing flag\n\n .gitignore                 |   1 +\n Documentation/git-hook.txt |  45 +++++++++++++\n Documentation/githooks.txt |   4 ++\n Makefile                   |   1 +\n builtin.h                  |   1 +\n builtin/am.c               |   8 ++-\n builtin/checkout.c         |  14 ++--\n builtin/clone.c            |   7 +-\n builtin/gc.c               |   3 +-\n builtin/hook.c             |  90 +++++++++++++++++++++++++\n builtin/merge.c            |   4 +-\n builtin/rebase.c           |   8 ++-\n builtin/receive-pack.c     |   7 +-\n builtin/worktree.c         |  28 ++++----\n command-list.txt           |   1 +\n commit.c                   |  15 +++--\n git-p4.py                  |  70 ++-----------------\n git-send-email.perl        |  22 +++---\n git.c                      |   1 +\n hook.c                     | 121 +++++++++++++++++++++++++++++++++\n hook.h                     |  56 ++++++++++++++++\n read-cache.c               |  11 ++-\n reset.c                    |  14 ++--\n run-command.c              |  32 ---------\n run-command.h              |  17 -----\n t/t1800-hook.sh            | 134 +++++++++++++++++++++++++++++++++++++\n t/t9001-send-email.sh      |   4 +-\n 27 files changed, 550 insertions(+), 169 deletions(-)\n create mode 100644 Documentation/git-hook.txt\n create mode 100644 builtin/hook.c\n create mode 100755 t/t1800-hook.sh\n\nRange-diff against v2:\n 1:  ba64faf0580 =  1:  02fd699e699 hook: add 'run' subcommand\n 2:  e3dc0aed81b =  2:  42cc4d2c3c6 gc: use hook library for pre-auto-gc hook\n 3:  6227a1e644d !  3:  cbbfd77a4f6 rebase: convert pre-rebase to use hook.h\n    @@ builtin/rebase.c\n      #define DEFAULT_REFLOG_ACTION \"rebase\"\n      \n     @@ builtin/rebase.c: 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    + \tint preserve_merges_selected = 0;\n     +\tstruct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;\n      \tstruct option builtin_rebase_options[] = {\n      \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n 4:  0e34eb54054 =  4:  b26cef24f39 am: convert applypatch to use hook.h\n 5:  a4df96c1719 !  5:  2a747a65829 hooks: convert 'post-checkout' hook to hook library\n    @@ hook.c: static int notify_hook_finished(int result,\n      \tstruct hook_cb_data cb_data = {\n      \t\t.rc = 0,\n      \t\t.hook_name = hook_name,\n    +-\t\t.hook_path = hook_path,\n    + \t\t.options = options,\n    + \t};\n    + \tint jobs = 1;\n     @@ hook.c: int run_hooks(const char *hook_name, const char *hook_path,\n      \tif (!options)\n      \t\tBUG(\"a struct run_hooks_opt must be provided to run_hooks\");\n    @@ hook.c: int run_hooks(const char *hook_name, const char *hook_path,\n     +\t\tstrbuf_add_absolute_path(&abs_path, hook_path);\n     +\t\thook_path = abs_path.buf;\n     +\t}\n    ++\tcb_data.hook_path = hook_path;\n     +\n      \trun_processes_parallel_tr2(jobs,\n      \t\t\t\t   pick_next_hook,\n    @@ hook.h: struct run_hooks_opt\n      \n      #define RUN_HOOKS_OPT_INIT { \\\n     \n    - ## read-cache.c ##\n    -@@\n    - #include \"sparse-index.h\"\n    - #include \"csum-file.h\"\n    - #include \"promisor-remote.h\"\n    -+#include \"hook.h\"\n    - \n    - /* Mask for the name length in ce_flags in the on-disk index */\n    - \n    -\n      ## reset.c ##\n     @@\n      #include \"tree-walk.h\"\n 6:  327f916f8c3 =  6:  7a9fd8627cd merge: convert post-merge to use hook.h\n 7:  328767015b1 =  7:  840fb530df3 git hook run: add an --ignore-missing flag\n 8:  6c4ebd68d56 =  8:  716ebabd794 send-email: use 'git hook run' for 'sendemail-validate'\n 9:  b1f52733e3c !  9:  95782109270 git-p4: use 'git hook' to run hooks\n    @@ Commit message\n         Python, we can directly call 'git hook run'. We emulate the existence\n         check with the --ignore-missing flag.\n     \n    -    As this is the last hook execution in git.git to not go through \"git\n    -    hook run\" or the hook.[ch] library we can now be absolutely sure that\n    -    our assertion in hook.c that only hooks known by the generated (from\n    -    githooks(5)) hook-list.h are permitted.\n    +    We're dropping the \"verbose\" handling added in 9f59ca4d6af (git-p4:\n    +    create new function run_git_hook, 2020-02-11), those who want\n    +    diagnostic output about how hooks are run are now able to get that via\n    +    e.g. the trace2 facility and GIT_TRACE=1.\n     \n         Signed-off-by: Emily Shaffer <emilyshaffer@google.com>\n         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n      ## git-p4.py ##\n     @@ git-p4.py: def decode_path(path):\n    -         return path\n      \n      def run_git_hook(cmd, param=[]):\n    --    \"\"\"Execute a hook if the hook exists.\"\"\"\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    @@ git-p4.py: def decode_path(path):\n     -            use_shell = True\n     -    return subprocess.call(cli, shell=use_shell)\n     -\n    -+    \"\"\"args are specified with -a <arg> -a <arg> -a <arg>\"\"\"\n     +    args = ['git', 'hook', 'run', '--ignore-missing', cmd]\n     +    if param:\n     +        args.append(\"--\")\n10:  dc31d98acdf = 10:  706426c8a79 commit: convert {pre-commit,prepare-commit-msg} hook to hook.h\n11:  58b7689e4af ! 11:  39069a9c3ff read-cache: convert post-index-change to use hook.h\n    @@ Commit message\n         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n      ## read-cache.c ##\n    +@@\n    + #include \"sparse-index.h\"\n    + #include \"csum-file.h\"\n    + #include \"promisor-remote.h\"\n    ++#include \"hook.h\"\n    + \n    + /* Mask for the name length in ce_flags in the on-disk index */\n    + \n     @@ read-cache.c: static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n      {\n      \tint ret;\n12:  ae1e2a82147 = 12:  9818078f1e5 receive-pack: convert push-to-checkout hook to hook.h\n13:  289d5a2d849 = 13:  1bc080d3611 run-command: remove old run_hook_{le,ve}() hook API\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"439059","messageId":"patch-v3-01.13-02fd699e699-20211019T231647Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v3-00.13-00000000000-20211019T231647Z-avarab@gmail.com","subject":"[PATCH v3 01/13] hook: add 'run' subcommand","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-19T23:20:39Z","receivedAt":"2021-10-19T23:21:32Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nIn order to enable hooks to be run as an external process, by a\nstandalone Git command, or by tools which wrap Git, provide an external\nmeans to run all configured hook commands for a given hook event.\n\nMost of our hooks require more complex functionality than this, but\nlet's start with the bare minimum required to support our simplest\nhooks.\n\nIn terms of implementation the usage_with_options() and \"goto usage\"\npattern here mirrors that of\nbuiltin/{commit-graph,multi-pack-index}.c.\n\nSome of the implementation here, such as a function being named\nrun_hooks() when it's tasked with running one hook, to using the\nrun_processes_parallel_tr2() API to run with jobs=1 is somewhere\nbetween a bit odd and and an overkill for the current features of this\n\"hook run\" command and the hook.[ch] API.\n\nThis code will eventually be able to run multiple hooks declared in\nconfig in parallel, by starting out with these names and APIs we\nreduce the later churn of renaming functions, switching from the\nrun_command() to run_processes_parallel_tr2() API etc.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n .gitignore                 |   1 +\n Documentation/git-hook.txt |  37 +++++++++++\n Documentation/githooks.txt |   4 ++\n Makefile                   |   1 +\n builtin.h                  |   1 +\n builtin/hook.c             |  85 ++++++++++++++++++++++++\n command-list.txt           |   1 +\n git.c                      |   1 +\n hook.c                     |  91 ++++++++++++++++++++++++++\n hook.h                     |  34 ++++++++++\n t/t1800-hook.sh            | 129 +++++++++++++++++++++++++++++++++++++\n 11 files changed, 385 insertions(+)\n create mode 100644 Documentation/git-hook.txt\n create mode 100644 builtin/hook.c\n create mode 100755 t/t1800-hook.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex 054249b20a8..f817c509ec0 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -77,6 +77,7 @@\n /git-grep\n /git-hash-object\n /git-help\n+/git-hook\n /git-http-backend\n /git-http-fetch\n /git-http-push\ndiff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt\nnew file mode 100644\nindex 00000000000..e39b1b5d069\n--- /dev/null\n+++ b/Documentation/git-hook.txt\n@@ -0,0 +1,37 @@\n+git-hook(1)\n+===========\n+\n+NAME\n+----\n+git-hook - Run git hooks\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git hook' run <hook-name> [-- <hook-args>]\n+\n+DESCRIPTION\n+-----------\n+\n+A command interface to running git hooks (see linkgit:githooks[5]),\n+for use by other scripted git commands.\n+\n+SUBCOMMANDS\n+-----------\n+\n+run::\n+\tRun the `<hook-name>` hook. See linkgit:githooks[5] for\n+\tsupported hook names.\n++\n+\n+Any positional arguments to the hook should be passed after a\n+mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See\n+linkgit:githooks[5] for arguments hooks might expect (if any).\n+\n+SEE ALSO\n+--------\n+linkgit:githooks[5]\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex b51959ff941..a16e62bc8c8 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -698,6 +698,10 @@ 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+SEE ALSO\n+--------\n+linkgit:git-hook[1]\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex 381bed2c1d2..1eef009def2 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1107,6 +1107,7 @@ BUILTIN_OBJS += builtin/get-tar-commit-id.o\n BUILTIN_OBJS += builtin/grep.o\n BUILTIN_OBJS += builtin/hash-object.o\n BUILTIN_OBJS += builtin/help.o\n+BUILTIN_OBJS += builtin/hook.o\n BUILTIN_OBJS += builtin/index-pack.o\n BUILTIN_OBJS += builtin/init-db.o\n BUILTIN_OBJS += builtin/interpret-trailers.o\ndiff --git a/builtin.h b/builtin.h\nindex 8a58743ed63..83379f3832c 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -164,6 +164,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);\n int cmd_grep(int argc, const char **argv, const char *prefix);\n int cmd_hash_object(int argc, const char **argv, const char *prefix);\n int cmd_help(int argc, const char **argv, const char *prefix);\n+int cmd_hook(int argc, const char **argv, const char *prefix);\n int cmd_index_pack(int argc, const char **argv, const char *prefix);\n int cmd_init_db(int argc, const char **argv, const char *prefix);\n int cmd_interpret_trailers(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/hook.c b/builtin/hook.c\nnew file mode 100644\nindex 00000000000..41dd15550cf\n--- /dev/null\n+++ b/builtin/hook.c\n@@ -0,0 +1,85 @@\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"config.h\"\n+#include \"hook.h\"\n+#include \"parse-options.h\"\n+#include \"strbuf.h\"\n+#include \"strvec.h\"\n+\n+#define BUILTIN_HOOK_RUN_USAGE \\\n+\tN_(\"git hook run <hook-name> [-- <hook-args>]\")\n+\n+static const char * const builtin_hook_usage[] = {\n+\tBUILTIN_HOOK_RUN_USAGE,\n+\tNULL\n+};\n+\n+static const char * const builtin_hook_run_usage[] = {\n+\tBUILTIN_HOOK_RUN_USAGE,\n+\tNULL\n+};\n+\n+static int run(int argc, const char **argv, const char *prefix)\n+{\n+\tint i;\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n+\tconst char *hook_name;\n+\tconst char *hook_path;\n+\tstruct option run_options[] = {\n+\t\tOPT_END(),\n+\t};\n+\tint ret;\n+\n+\targc = parse_options(argc, argv, prefix, run_options,\n+\t\t\t     builtin_hook_run_usage,\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n+\n+\tif (!argc)\n+\t\tgoto usage;\n+\n+\t/*\n+\t * Having a -- for \"run\" when providing <hook-args> is\n+\t * mandatory.\n+\t */\n+\tif (argc > 1 && strcmp(argv[1], \"--\") &&\n+\t    strcmp(argv[1], \"--end-of-options\"))\n+\t\tgoto usage;\n+\n+\t/* Add our arguments, start after -- */\n+\tfor (i = 2 ; i < argc; i++)\n+\t\tstrvec_push(&opt.args, argv[i]);\n+\n+\t/* Need to take into account core.hooksPath */\n+\tgit_config(git_default_config, NULL);\n+\n+\thook_name = argv[0];\n+\thook_path = find_hook(hook_name);\n+\tif (!hook_path) {\n+\t\terror(\"cannot find a hook named %s\", hook_name);\n+\t\treturn 1;\n+\t}\n+\n+\tret = run_hooks(hook_name, hook_path, &opt);\n+\trun_hooks_opt_clear(&opt);\n+\treturn ret;\n+usage:\n+\tusage_with_options(builtin_hook_run_usage, run_options);\n+}\n+\n+int cmd_hook(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct option builtin_hook_options[] = {\n+\t\tOPT_END(),\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, builtin_hook_options,\n+\t\t\t     builtin_hook_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n+\tif (!argc)\n+\t\tgoto usage;\n+\n+\tif (!strcmp(argv[0], \"run\"))\n+\t\treturn run(argc, argv, prefix);\n+\n+usage:\n+\tusage_with_options(builtin_hook_usage, builtin_hook_options);\n+}\ndiff --git a/command-list.txt b/command-list.txt\nindex a289f09ed6f..17c3958802e 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -103,6 +103,7 @@ git-grep                                mainporcelain           info\n git-gui                                 mainporcelain\n git-hash-object                         plumbingmanipulators\n git-help                                ancillaryinterrogators          complete\n+git-hook                                purehelpers\n git-http-backend                        synchingrepositories\n git-http-fetch                          synchelpers\n git-http-push                           synchelpers\ndiff --git a/git.c b/git.c\nindex 5ff21be21f3..a5be02b04b8 100644\n--- a/git.c\n+++ b/git.c\n@@ -538,6 +538,7 @@ static struct cmd_struct commands[] = {\n \t{ \"grep\", cmd_grep, RUN_SETUP_GENTLY },\n \t{ \"hash-object\", cmd_hash_object },\n \t{ \"help\", cmd_help },\n+\t{ \"hook\", cmd_hook, RUN_SETUP },\n \t{ \"index-pack\", cmd_index_pack, RUN_SETUP_GENTLY | NO_PARSEOPT },\n \t{ \"init\", cmd_init_db },\n \t{ \"init-db\", cmd_init_db },\ndiff --git a/hook.c b/hook.c\nindex 55e1145a4b7..20a1a4fec33 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -1,6 +1,7 @@\n #include \"cache.h\"\n #include \"hook.h\"\n #include \"run-command.h\"\n+#include \"config.h\"\n \n const char *find_hook(const char *name)\n {\n@@ -40,3 +41,93 @@ int hook_exists(const char *name)\n {\n \treturn !!find_hook(name);\n }\n+\n+void run_hooks_opt_clear(struct run_hooks_opt *o)\n+{\n+\tstrvec_clear(&o->env);\n+\tstrvec_clear(&o->args);\n+}\n+\n+static int pick_next_hook(struct child_process *cp,\n+\t\t\t  struct strbuf *out,\n+\t\t\t  void *pp_cb,\n+\t\t\t  void **pp_task_cb)\n+{\n+\tstruct hook_cb_data *hook_cb = pp_cb;\n+\tconst char *hook_path = hook_cb->hook_path;\n+\n+\tif (!hook_path)\n+\t\treturn 0;\n+\n+\tcp->no_stdin = 1;\n+\tcp->env = hook_cb->options->env.v;\n+\tcp->stdout_to_stderr = 1;\n+\tcp->trace2_hook_name = hook_cb->hook_name;\n+\n+\tstrvec_push(&cp->args, hook_path);\n+\tstrvec_pushv(&cp->args, hook_cb->options->args.v);\n+\n+\t/* Provide context for errors if necessary */\n+\t*pp_task_cb = (char *)hook_path;\n+\n+\t/*\n+\t * This pick_next_hook() will be called again, we're only\n+\t * running one hook, so indicate that no more work will be\n+\t * done.\n+\t */\n+\thook_cb->hook_path = NULL;\n+\n+\treturn 1;\n+}\n+\n+static int notify_start_failure(struct strbuf *out,\n+\t\t\t\tvoid *pp_cb,\n+\t\t\t\tvoid *pp_task_cp)\n+{\n+\tstruct hook_cb_data *hook_cb = pp_cb;\n+\tconst char *hook_path = pp_task_cp;\n+\n+\thook_cb->rc |= 1;\n+\n+\tstrbuf_addf(out, _(\"Couldn't start hook '%s'\\n\"),\n+\t\t    hook_path);\n+\n+\treturn 1;\n+}\n+\n+static int notify_hook_finished(int result,\n+\t\t\t\tstruct strbuf *out,\n+\t\t\t\tvoid *pp_cb,\n+\t\t\t\tvoid *pp_task_cb)\n+{\n+\tstruct hook_cb_data *hook_cb = pp_cb;\n+\n+\thook_cb->rc |= result;\n+\n+\treturn 0;\n+}\n+\n+int run_hooks(const char *hook_name, const char *hook_path,\n+\t      struct run_hooks_opt *options)\n+{\n+\tstruct hook_cb_data cb_data = {\n+\t\t.rc = 0,\n+\t\t.hook_name = hook_name,\n+\t\t.hook_path = hook_path,\n+\t\t.options = options,\n+\t};\n+\tint jobs = 1;\n+\n+\tif (!options)\n+\t\tBUG(\"a struct run_hooks_opt must be provided to run_hooks\");\n+\n+\trun_processes_parallel_tr2(jobs,\n+\t\t\t\t   pick_next_hook,\n+\t\t\t\t   notify_start_failure,\n+\t\t\t\t   notify_hook_finished,\n+\t\t\t\t   &cb_data,\n+\t\t\t\t   \"hook\",\n+\t\t\t\t   hook_name);\n+\n+\treturn cb_data.rc;\n+}\ndiff --git a/hook.h b/hook.h\nindex 6aa36fc7ff9..a9317c3f95e 100644\n--- a/hook.h\n+++ b/hook.h\n@@ -1,5 +1,28 @@\n #ifndef HOOK_H\n #define HOOK_H\n+#include \"strvec.h\"\n+\n+struct run_hooks_opt\n+{\n+\t/* Environment vars to be set for each hook */\n+\tstruct strvec env;\n+\n+\t/* Args to be passed to each hook */\n+\tstruct strvec args;\n+};\n+\n+#define RUN_HOOKS_OPT_INIT { \\\n+\t.env = STRVEC_INIT, \\\n+\t.args = STRVEC_INIT, \\\n+}\n+\n+struct hook_cb_data {\n+\t/* rc reflects the cumulative failure state */\n+\tint rc;\n+\tconst char *hook_name;\n+\tconst char *hook_path;\n+\tstruct run_hooks_opt *options;\n+};\n \n /*\n  * Returns the path to the hook file, or NULL if the hook is missing\n@@ -13,4 +36,15 @@ const char *find_hook(const char *name);\n  */\n int hook_exists(const char *hookname);\n \n+/**\n+ * Clear data from an initialized \"struct run_hooks_opt\".\n+ */\n+void run_hooks_opt_clear(struct run_hooks_opt *o);\n+\n+/**\n+ * Takes an already resolved hook found via find_hook() and runs\n+ * it. Does not call run_hooks_opt_clear() for you.\n+ */\n+int run_hooks(const char *hookname, const char *hook_path,\n+\t      struct run_hooks_opt *options);\n #endif\ndiff --git a/t/t1800-hook.sh b/t/t1800-hook.sh\nnew file mode 100755\nindex 00000000000..3aea1b105f0\n--- /dev/null\n+++ b/t/t1800-hook.sh\n@@ -0,0 +1,129 @@\n+#!/bin/sh\n+\n+test_description='git-hook command'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n+. ./test-lib.sh\n+\n+test_expect_success 'git hook usage' '\n+\ttest_expect_code 129 git hook &&\n+\ttest_expect_code 129 git hook run &&\n+\ttest_expect_code 129 git hook run -h &&\n+\ttest_expect_code 129 git hook run --unknown 2>err &&\n+\tgrep \"unknown option\" err\n+'\n+\n+test_expect_success 'git hook run: nonexistent hook' '\n+\tcat >stderr.expect <<-\\EOF &&\n+\terror: cannot find a hook named test-hook\n+\tEOF\n+\ttest_expect_code 1 git hook run test-hook 2>stderr.actual &&\n+\ttest_cmp stderr.expect stderr.actual\n+'\n+\n+test_expect_success 'git hook run: basic' '\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\techo Test hook\n+\tEOF\n+\n+\tcat >expect <<-\\EOF &&\n+\tTest hook\n+\tEOF\n+\tgit hook run test-hook 2>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git hook run: stdout and stderr both write to our stderr' '\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\techo >&1 Will end up on stderr\n+\techo >&2 Will end up on stderr\n+\tEOF\n+\n+\tcat >stderr.expect <<-\\EOF &&\n+\tWill end up on stderr\n+\tWill end up on stderr\n+\tEOF\n+\tgit hook run test-hook >stdout.actual 2>stderr.actual &&\n+\ttest_cmp stderr.expect stderr.actual &&\n+\ttest_must_be_empty stdout.actual\n+'\n+\n+test_expect_success 'git hook run: exit codes are passed along' '\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\texit 1\n+\tEOF\n+\n+\ttest_expect_code 1 git hook run test-hook &&\n+\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\texit 2\n+\tEOF\n+\n+\ttest_expect_code 2 git hook run test-hook &&\n+\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\texit 128\n+\tEOF\n+\n+\ttest_expect_code 128 git hook run test-hook &&\n+\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\texit 129\n+\tEOF\n+\n+\ttest_expect_code 129 git hook run test-hook\n+'\n+\n+test_expect_success 'git hook run arg u ments without -- is not allowed' '\n+\ttest_expect_code 129 git hook run test-hook arg u ments\n+'\n+\n+test_expect_success 'git hook run -- pass arguments' '\n+\twrite_script .git/hooks/test-hook <<-\\EOF &&\n+\techo $1\n+\techo $2\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\targ\n+\tu ments\n+\tEOF\n+\n+\tgit hook run test-hook -- arg \"u ments\" 2>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git hook run -- out-of-repo runs excluded' '\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\techo Test hook\n+\tEOF\n+\n+\tnongit test_must_fail git hook run test-hook\n+'\n+\n+test_expect_success 'git -c core.hooksPath=<PATH> hook run' '\n+\tmkdir my-hooks &&\n+\twrite_script my-hooks/test-hook <<-\\EOF &&\n+\techo Hook ran $1 >>actual\n+\tEOF\n+\n+\tcat >expect <<-\\EOF &&\n+\tTest hook\n+\tHook ran one\n+\tHook ran two\n+\tHook ran three\n+\tHook ran four\n+\tEOF\n+\n+\t# Test various ways of specifying the path. See also\n+\t# t1350-config-hooks-path.sh\n+\t>actual &&\n+\tgit hook run test-hook -- ignored 2>>actual &&\n+\tgit -c core.hooksPath=my-hooks hook run test-hook -- one 2>>actual &&\n+\tgit -c core.hooksPath=my-hooks/ hook run test-hook -- two 2>>actual &&\n+\tgit -c core.hooksPath=\"$PWD/my-hooks\" hook run test-hook -- three 2>>actual &&\n+\tgit -c core.hooksPath=\"$PWD/my-hooks/\" hook run test-hook -- four 2>>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"439060","messageId":"patch-v3-02.13-42cc4d2c3c6-20211019T231647Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v3-00.13-00000000000-20211019T231647Z-avarab@gmail.com","subject":"[PATCH v3 02/13] gc: use hook library for pre-auto-gc hook","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-19T23:20:40Z","receivedAt":"2021-10-19T23:21:33Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the pre-auto-gc hook away from run-command.h to and over to the\nnew hook.h library.\n\nTo do this introduce a simple run_hooks_oneshot() wrapper, we'll be\nusing it extensively for these simple cases of wanting to run a single\nhook under a given name, and having it free the memory we allocate for\nus.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/gc.c |  3 ++-\n hook.c       | 20 ++++++++++++++++++++\n hook.h       | 13 +++++++++++++\n 3 files changed, 35 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 6b3de3dd514..95939e77f53 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@@ -394,7 +395,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_oneshot(\"pre-auto-gc\", NULL))\n \t\treturn 0;\n \treturn 1;\n }\ndiff --git a/hook.c b/hook.c\nindex 20a1a4fec33..9951015dc66 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -131,3 +131,23 @@ int run_hooks(const char *hook_name, const char *hook_path,\n \n \treturn cb_data.rc;\n }\n+\n+int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options)\n+{\n+\tconst char *hook_path;\n+\tstruct run_hooks_opt hook_opt_scratch = RUN_HOOKS_OPT_INIT;\n+\tint ret = 0;\n+\n+\tif (!options)\n+\t\toptions = &hook_opt_scratch;\n+\n+\thook_path = find_hook(hook_name);\n+\tif (!hook_path)\n+\t\tgoto cleanup;\n+\n+\tret = run_hooks(hook_name, hook_path, options);\n+cleanup:\n+\trun_hooks_opt_clear(options);\n+\n+\treturn ret;\n+}\ndiff --git a/hook.h b/hook.h\nindex a9317c3f95e..9e4d10df63e 100644\n--- a/hook.h\n+++ b/hook.h\n@@ -44,7 +44,20 @@ void run_hooks_opt_clear(struct run_hooks_opt *o);\n /**\n  * Takes an already resolved hook found via find_hook() and runs\n  * it. Does not call run_hooks_opt_clear() for you.\n+ *\n+ * See run_hooks_oneshot() for the simpler one-shot API.\n  */\n int run_hooks(const char *hookname, const char *hook_path,\n \t      struct run_hooks_opt *options);\n+\n+/**\n+ * Calls find_hook() on your \"hook_name\" and runs the hooks (if any)\n+ * with run_hooks().\n+ *\n+ * If \"options\" is provided calls run_hooks_opt_clear() on it for\n+ * you. If \"options\" is NULL the default options from\n+ * RUN_HOOKS_OPT_INIT will be used.\n+ */\n+int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options);\n+\n #endif\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"439061","messageId":"patch-v3-03.13-cbbfd77a4f6-20211019T231647Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v3-00.13-00000000000-20211019T231647Z-avarab@gmail.com","subject":"[PATCH v3 03/13] rebase: convert pre-rebase to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-19T23:20:41Z","receivedAt":"2021-10-19T23:21:37Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the pre-rebase hook away from run-command.h to and over to the\nnew hook.h library.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/rebase.c | 8 ++++++--\n 1 file changed, 6 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 34b4744e5f3..758b5dfabe2 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@@ -1017,6 +1018,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tint reschedule_failed_exec = -1;\n \tint allow_preemptive_ff = 1;\n \tint preserve_merges_selected = 0;\n+\tstruct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;\n \tstruct option builtin_rebase_options[] = {\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n@@ -1711,9 +1713,11 @@ 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_push(&hook_opt.args, options.upstream_arg);\n+\tif (argc)\n+\t\tstrvec_push(&hook_opt.args, argv[0]);\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_oneshot(\"pre-rebase\", &hook_opt))\n \t\tdie(_(\"The pre-rebase hook refused to rebase.\"));\n \n \tif (options.flags & REBASE_DIFFSTAT) {\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"439062","messageId":"patch-v3-04.13-b26cef24f39-20211019T231647Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v3-00.13-00000000000-20211019T231647Z-avarab@gmail.com","subject":"[PATCH v3 04/13] am: convert applypatch to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-19T23:20:42Z","receivedAt":"2021-10-19T23:21:39Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nTeach pre-applypatch, post-applypatch, and applypatch-msg to use the\nhook.h library instead of the run-command.h library.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/am.c | 8 +++++---\n 1 file changed, 5 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..5528f131a81 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -446,9 +446,11 @@ 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;\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_oneshot(\"applypatch-msg\", &opt);\n \n \tif (!ret) {\n \t\tFREE_AND_NULL(state->msg);\n@@ -1609,7 +1611,7 @@ static void do_commit(const struct am_state *state)\n \tconst char *reflog_msg, *author, *committer = NULL;\n \tstruct strbuf sb = STRBUF_INIT;\n \n-\tif (run_hook_le(NULL, \"pre-applypatch\", NULL))\n+\tif (run_hooks_oneshot(\"pre-applypatch\", NULL))\n \t\texit(1);\n \n \tif (write_cache_as_tree(&tree, 0, NULL))\n@@ -1661,7 +1663,7 @@ 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_oneshot(\"post-applypatch\", NULL);\n \n \tstrbuf_release(&sb);\n }\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"439063","messageId":"patch-v3-05.13-2a747a65829-20211019T231647Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v3-00.13-00000000000-20211019T231647Z-avarab@gmail.com","subject":"[PATCH v3 05/13] hooks: convert 'post-checkout' hook to hook library","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-19T23:20:43Z","receivedAt":"2021-10-19T23:21:40Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the running of the 'post-checkout' hook away from run-command.h\nto the new hook.h library. For \"worktree\" this requires a change to it\nto run the hooks from a given directory.\n\nWe could strictly speaking skip the \"absolute_path\" flag and just\ncheck if \"dir\" is specified, but let's split them up for clarity, as\nwell as for any future user who'd like to set \"dir\" but not implicitly\nchange the argument to an absolute path.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/checkout.c | 14 +++++++++-----\n builtin/clone.c    |  7 +++++--\n builtin/worktree.c | 28 ++++++++++++----------------\n hook.c             | 12 +++++++++++-\n hook.h             |  9 +++++++++\n reset.c            | 14 ++++++++++----\n 6 files changed, 56 insertions(+), 28 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex cbf73b8c9f6..54cbba821a8 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@@ -106,13 +107,16 @@ 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;\n+\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+\treturn run_hooks_oneshot(\"post-checkout\", &opt);\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 559acf9e036..53ca5ca0eb2 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@@ -659,6 +660,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;\n \n \tif (option_no_checkout)\n \t\treturn 0;\n@@ -705,8 +707,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),\n+\t\t     \"1\", NULL);\n+\terr |= run_hooks_oneshot(\"post-checkout\", &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 d22ece93e1a..330867c19bf 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -382,22 +382,18 @@ 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;\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+\t\topt.absolute_path = 1;\n+\n+\t\tret = run_hooks_oneshot(\"post-checkout\", &opt);\n \t}\n \n \tstrvec_clear(&child_env);\ndiff --git a/hook.c b/hook.c\nindex 9951015dc66..bf34cd0a2c1 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -63,6 +63,7 @@ static int pick_next_hook(struct child_process *cp,\n \tcp->env = hook_cb->options->env.v;\n \tcp->stdout_to_stderr = 1;\n \tcp->trace2_hook_name = hook_cb->hook_name;\n+\tcp->dir = hook_cb->options->dir;\n \n \tstrvec_push(&cp->args, hook_path);\n \tstrvec_pushv(&cp->args, hook_cb->options->args.v);\n@@ -110,10 +111,10 @@ static int notify_hook_finished(int result,\n int run_hooks(const char *hook_name, const char *hook_path,\n \t      struct run_hooks_opt *options)\n {\n+\tstruct strbuf abs_path = STRBUF_INIT;\n \tstruct hook_cb_data cb_data = {\n \t\t.rc = 0,\n \t\t.hook_name = hook_name,\n-\t\t.hook_path = hook_path,\n \t\t.options = options,\n \t};\n \tint jobs = 1;\n@@ -121,6 +122,12 @@ int run_hooks(const char *hook_name, const char *hook_path,\n \tif (!options)\n \t\tBUG(\"a struct run_hooks_opt must be provided to run_hooks\");\n \n+\tif (options->absolute_path) {\n+\t\tstrbuf_add_absolute_path(&abs_path, hook_path);\n+\t\thook_path = abs_path.buf;\n+\t}\n+\tcb_data.hook_path = hook_path;\n+\n \trun_processes_parallel_tr2(jobs,\n \t\t\t\t   pick_next_hook,\n \t\t\t\t   notify_start_failure,\n@@ -129,6 +136,9 @@ int run_hooks(const char *hook_name, const char *hook_path,\n \t\t\t\t   \"hook\",\n \t\t\t\t   hook_name);\n \n+\tif (options->absolute_path)\n+\t\tstrbuf_release(&abs_path);\n+\n \treturn cb_data.rc;\n }\n \ndiff --git a/hook.h b/hook.h\nindex 9e4d10df63e..252a605b125 100644\n--- a/hook.h\n+++ b/hook.h\n@@ -9,6 +9,15 @@ struct run_hooks_opt\n \n \t/* Args to be passed to each hook */\n \tstruct strvec args;\n+\n+\t/*\n+\t * Resolve and run the \"absolute_path(hook)\" instead of\n+\t * \"hook\". Used for \"git worktree\" hooks\n+\t */\n+\tint absolute_path;\n+\n+\t/* Path to initial working directory for subprocess */\n+\tconst char *dir;\n };\n \n #define RUN_HOOKS_OPT_INIT { \\\ndiff --git a/reset.c b/reset.c\nindex f214df3d96c..4234dd345db 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,15 @@ 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;\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_oneshot(\"post-checkout\", &opt);\n+\t}\n \n leave_reset_head:\n \tstrbuf_release(&msg);\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"439064","messageId":"patch-v3-06.13-7a9fd8627cd-20211019T231647Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v3-00.13-00000000000-20211019T231647Z-avarab@gmail.com","subject":"[PATCH v3 06/13] merge: convert post-merge to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-19T23:20:44Z","receivedAt":"2021-10-19T23:21:41Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nTeach post-merge to use the hook.h library instead of the\nrun-command.h library to run hooks.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/merge.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex cc4a910c69b..c48b875b82b 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -448,6 +448,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;\n \tconst struct object_id *head = &head_commit->object.oid;\n \n \tif (!msg)\n@@ -488,7 +489,8 @@ 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_oneshot(\"post-merge\", &opt);\n \n \tapply_autostash(git_path_merge_autostash(the_repository));\n \tstrbuf_release(&reflog_message);\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"439065","messageId":"patch-v3-07.13-840fb530df3-20211019T231647Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v3-00.13-00000000000-20211019T231647Z-avarab@gmail.com","subject":"[PATCH v3 07/13] git hook run: add an --ignore-missing flag","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-19T23:20:45Z","receivedAt":"2021-10-19T23:21:46Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"For certain one-shot hooks we'd like to optimistically run them, and\nnot complain if they don't exist. This will be used by send-email in a\nsubsequent commit.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Documentation/git-hook.txt | 10 +++++++++-\n builtin/hook.c             |  7 ++++++-\n t/t1800-hook.sh            |  5 +++++\n 3 files changed, 20 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt\nindex e39b1b5d069..77c3a8ad909 100644\n--- a/Documentation/git-hook.txt\n+++ b/Documentation/git-hook.txt\n@@ -8,7 +8,7 @@ git-hook - Run git hooks\n SYNOPSIS\n --------\n [verse]\n-'git hook' run <hook-name> [-- <hook-args>]\n+'git hook' run [--ignore-missing] <hook-name> [-- <hook-args>]\n \n DESCRIPTION\n -----------\n@@ -28,6 +28,14 @@ Any positional arguments to the hook should be passed after a\n mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See\n linkgit:githooks[5] for arguments hooks might expect (if any).\n \n+OPTIONS\n+-------\n+\n+--ignore-missing::\n+\tIgnore any missing hook by quietly returning zero. Used for\n+\ttools that want to do a blind one-shot run of a hook that may\n+\tor may not be present.\n+\n SEE ALSO\n --------\n linkgit:githooks[5]\ndiff --git a/builtin/hook.c b/builtin/hook.c\nindex 41dd15550cf..fa26454990d 100644\n--- a/builtin/hook.c\n+++ b/builtin/hook.c\n@@ -7,7 +7,7 @@\n #include \"strvec.h\"\n \n #define BUILTIN_HOOK_RUN_USAGE \\\n-\tN_(\"git hook run <hook-name> [-- <hook-args>]\")\n+\tN_(\"git hook run [--ignore-missing] <hook-name> [-- <hook-args>]\")\n \n static const char * const builtin_hook_usage[] = {\n \tBUILTIN_HOOK_RUN_USAGE,\n@@ -23,9 +23,12 @@ static int run(int argc, const char **argv, const char *prefix)\n {\n \tint i;\n \tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n+\tint ignore_missing = 0;\n \tconst char *hook_name;\n \tconst char *hook_path;\n \tstruct option run_options[] = {\n+\t\tOPT_BOOL(0, \"ignore-missing\", &ignore_missing,\n+\t\t\t N_(\"silently ignore missing requested <hook-name>\")),\n \t\tOPT_END(),\n \t};\n \tint ret;\n@@ -53,6 +56,8 @@ static int run(int argc, const char **argv, const char *prefix)\n \tgit_config(git_default_config, NULL);\n \n \thook_name = argv[0];\n+\tif (ignore_missing)\n+\t\treturn run_hooks_oneshot(hook_name, &opt);\n \thook_path = find_hook(hook_name);\n \tif (!hook_path) {\n \t\terror(\"cannot find a hook named %s\", hook_name);\ndiff --git a/t/t1800-hook.sh b/t/t1800-hook.sh\nindex 3aea1b105f0..29718aa9913 100755\n--- a/t/t1800-hook.sh\n+++ b/t/t1800-hook.sh\n@@ -21,6 +21,11 @@ test_expect_success 'git hook run: nonexistent hook' '\n \ttest_cmp stderr.expect stderr.actual\n '\n \n+test_expect_success 'git hook run: nonexistent hook with --ignore-missing' '\n+\tgit hook run --ignore-missing does-not-exist 2>stderr.actual &&\n+\ttest_must_be_empty stderr.actual\n+'\n+\n test_expect_success 'git hook run: basic' '\n \twrite_script .git/hooks/test-hook <<-EOF &&\n \techo Test hook\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"439066","messageId":"patch-v3-08.13-716ebabd794-20211019T231647Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v3-00.13-00000000000-20211019T231647Z-avarab@gmail.com","subject":"[PATCH v3 08/13] send-email: use 'git hook run' for 'sendemail-validate'","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-19T23:20:46Z","receivedAt":"2021-10-19T23:21:47Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nChange the \"sendmail-validate\" hook to be run via the \"git hook run\"\nwrapper instead of via a direct invocation.\n\nThis is the smallest possibly change to get \"send-email\" using \"git\nhook run\". We still check the hook itself with \"-x\", and set a\n\"GIT_DIR\" variable, both of which are asserted by our tests. We'll\nneed to get rid of this special behavior if we start running N hooks,\nbut for now let's be as close to bug-for-bug compatible as possible.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl   | 22 ++++++++++++++--------\n t/t9001-send-email.sh |  4 ++--\n 2 files changed, 16 insertions(+), 10 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 5262d88ee32..4c20c0bbb79 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -195,13 +195,13 @@ sub format_2822_time {\n my $editor;\n \n sub system_or_msg {\n-\tmy ($args, $msg) = @_;\n+\tmy ($args, $msg, $cmd_name) = @_;\n \tsystem(@$args);\n \tmy $signalled = $? & 127;\n \tmy $exit_code = $? >> 8;\n \treturn unless $signalled or $exit_code;\n \n-\tmy @sprintf_args = ($args->[0], $exit_code);\n+\tmy @sprintf_args = ($cmd_name ? $cmd_name : $args->[0], $exit_code);\n \tif (defined $msg) {\n \t\t# Quiet the 'redundant' warning category, except we\n \t\t# need to support down to Perl 5.8, so we can't do a\n@@ -2039,10 +2039,10 @@ sub validate_patch {\n \tmy ($fn, $xfer_encoding) = @_;\n \n \tif ($repo) {\n+\t\tmy $hook_name = 'sendemail-validate';\n \t\tmy $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks');\n \t\trequire File::Spec;\n-\t\tmy $validate_hook = File::Spec->catfile($hooks_path,\n-\t\t\t\t\t    'sendemail-validate');\n+\t\tmy $validate_hook = File::Spec->catfile($hooks_path, $hook_name);\n \t\tmy $hook_error;\n \t\tif (-x $validate_hook) {\n \t\t\trequire Cwd;\n@@ -2052,13 +2052,19 @@ sub validate_patch {\n \t\t\tchdir($repo->wc_path() or $repo->repo_path())\n \t\t\t\tor die(\"chdir: $!\");\n \t\t\tlocal $ENV{\"GIT_DIR\"} = $repo->repo_path();\n-\t\t\t$hook_error = system_or_msg([$validate_hook, $target]);\n+\t\t\tmy @cmd = (\"git\", \"hook\", \"run\", \"--ignore-missing\",\n+\t\t\t\t    $hook_name, \"--\");\n+\t\t\tmy @cmd_msg = (@cmd, \"<patch>\");\n+\t\t\tmy @cmd_run = (@cmd, $target);\n+\t\t\t$hook_error = system_or_msg(\\@cmd_run, undef, \"@cmd_msg\");\n \t\t\tchdir($cwd_save) or die(\"chdir: $!\");\n \t\t}\n \t\tif ($hook_error) {\n-\t\t\tdie sprintf(__(\"fatal: %s: rejected by sendemail-validate hook\\n\" .\n-\t\t\t\t       \"%s\\n\" .\n-\t\t\t\t       \"warning: no patches were sent\\n\"), $fn, $hook_error);\n+\t\t\t$hook_error = sprintf(__(\"fatal: %s: rejected by %s hook\\n\" .\n+\t\t\t\t\t\t $hook_error . \"\\n\" .\n+\t\t\t\t\t\t \"warning: no patches were sent\\n\"),\n+\t\t\t\t\t      $fn, $hook_name);\n+\t\t\tdie $hook_error;\n \t\t}\n \t}\n \ndiff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\nindex aa0c20499ba..84d0f40d76a 100755\n--- a/t/t9001-send-email.sh\n+++ b/t/t9001-send-email.sh\n@@ -539,7 +539,7 @@ test_expect_success $PREREQ \"--validate respects relative core.hooksPath path\" '\n \ttest_path_is_file my-hooks.ran &&\n \tcat >expect <<-EOF &&\n \tfatal: longline.patch: rejected by sendemail-validate hook\n-\tfatal: command '\"'\"'my-hooks/sendemail-validate'\"'\"' died with exit code 1\n+\tfatal: command '\"'\"'git hook run --ignore-missing sendemail-validate -- <patch>'\"'\"' died with exit code 1\n \twarning: no patches were sent\n \tEOF\n \ttest_cmp expect actual\n@@ -558,7 +558,7 @@ test_expect_success $PREREQ \"--validate respects absolute core.hooksPath path\" '\n \ttest_path_is_file my-hooks.ran &&\n \tcat >expect <<-EOF &&\n \tfatal: longline.patch: rejected by sendemail-validate hook\n-\tfatal: command '\"'\"'$hooks_path/sendemail-validate'\"'\"' died with exit code 1\n+\tfatal: command '\"'\"'git hook run --ignore-missing sendemail-validate -- <patch>'\"'\"' died with exit code 1\n \twarning: no patches were sent\n \tEOF\n \ttest_cmp expect actual\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"439067","messageId":"patch-v3-09.13-95782109270-20211019T231647Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v3-00.13-00000000000-20211019T231647Z-avarab@gmail.com","subject":"[PATCH v3 09/13] git-p4: use 'git hook' to run hooks","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-19T23:20:47Z","receivedAt":"2021-10-19T23:21:49Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nInstead of duplicating the behavior of run-command.h:run_hook_le() in\nPython, we can directly call 'git hook run'. We emulate the existence\ncheck with the --ignore-missing flag.\n\nWe're dropping the \"verbose\" handling added in 9f59ca4d6af (git-p4:\ncreate new function run_git_hook, 2020-02-11), those who want\ndiagnostic output about how hooks are run are now able to get that via\ne.g. the trace2 facility and GIT_TRACE=1.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-p4.py | 70 +++++--------------------------------------------------\n 1 file changed, 6 insertions(+), 64 deletions(-)\n\ndiff --git a/git-p4.py b/git-p4.py\nindex 2b4500226aa..3b54168eb4a 100755\n--- a/git-p4.py\n+++ b/git-p4.py\n@@ -208,70 +208,12 @@ 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-        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-\n+    args = ['git', 'hook', 'run', '--ignore-missing', cmd]\n+    if param:\n+        args.append(\"--\")\n+        for p in param:\n+            args.append(p)\n+    return subprocess.call(args) == 0\n \n def write_pipe(c, stdin):\n     if verbose:\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"439068","messageId":"patch-v3-10.13-706426c8a79-20211019T231647Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v3-00.13-00000000000-20211019T231647Z-avarab@gmail.com","subject":"[PATCH v3 10/13] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-19T23:20:48Z","receivedAt":"2021-10-19T23:21:52Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove these hooks hook away from run-command.h to and over to the new\nhook.h library.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n commit.c | 15 ++++++++-------\n 1 file changed, 8 insertions(+), 7 deletions(-)\n\ndiff --git a/commit.c b/commit.c\nindex 551de4903c1..4e7cbd7585d 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@@ -1700,22 +1701,22 @@ size_t ignore_non_trailer(const char *buf, size_t len)\n int run_commit_hook(int editor_is_used, 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;\n \tva_list args;\n-\tint ret;\n+\tconst char *arg;\n \n-\tstrvec_pushf(&hook_env, \"GIT_INDEX_FILE=%s\", index_file);\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-\treturn ret;\n+\treturn run_hooks_oneshot(name, &opt);\n }\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"439069","messageId":"patch-v3-11.13-39069a9c3ff-20211019T231647Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v3-00.13-00000000000-20211019T231647Z-avarab@gmail.com","subject":"[PATCH v3 11/13] read-cache: convert post-index-change to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-19T23:20:49Z","receivedAt":"2021-10-19T23:21:55Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the post-index-change hook away from run-command.h to and over to\nthe new hook.h library.\n\nThis removes the last direct user of \"run_hook_ve()\" outside of\nrun-command.c (\"run_hook_le()\" still uses it). So we can make the\nfunction static now. A subsequent commit will remove this code\nentirely when \"run_hook_le()\" itself goes away.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n read-cache.c  | 11 ++++++++---\n run-command.c |  2 +-\n run-command.h |  1 -\n 3 files changed, 9 insertions(+), 5 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex 8a50ff66b30..6674df28ab3 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -28,6 +28,7 @@\n #include \"sparse-index.h\"\n #include \"csum-file.h\"\n #include \"promisor-remote.h\"\n+#include \"hook.h\"\n \n /* Mask for the name length in ce_flags in the on-disk index */\n \n@@ -3101,6 +3102,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n {\n \tint ret;\n \tint was_full = !istate->sparse_index;\n+\tstruct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;\n \n \tret = convert_to_sparse(istate, 0);\n \n@@ -3129,9 +3131,12 @@ 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_oneshot(\"post-index-change\", &hook_opt);\n+\n \tistate->updated_workdir = 0;\n \tistate->updated_skipworktree = 0;\n \ndiff --git a/run-command.c b/run-command.c\nindex 7ef5cc712a9..d92e670c8ed 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -1323,7 +1323,7 @@ int async_with_fork(void)\n #endif\n }\n \n-int run_hook_ve(const char *const *env, const char *name, va_list args)\n+static 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;\ndiff --git a/run-command.h b/run-command.h\nindex 49878262584..3fa7454cf8a 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -239,7 +239,6 @@ int run_command(struct child_process *);\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-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"439070","messageId":"patch-v3-12.13-9818078f1e5-20211019T231647Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v3-00.13-00000000000-20211019T231647Z-avarab@gmail.com","subject":"[PATCH v3 12/13] receive-pack: convert push-to-checkout hook to hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-19T23:20:50Z","receivedAt":"2021-10-19T23:21:59Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the push-to-checkout hook away from run-command.h to and over to\nthe new hook.h library.\n\nThis removes the last direct user of run_hook_le(), so we could remove\nthat function now, but let's leave that to a follow-up cleanup commit.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/receive-pack.c | 7 +++++--\n 1 file changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 25cc0c907e1..22e7a431728 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1435,9 +1435,12 @@ 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;\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_oneshot(push_to_checkout_hook, &opt))\n \t\treturn \"push-to-checkout hook declined\";\n \telse\n \t\treturn NULL;\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"439071","messageId":"patch-v3-13.13-1bc080d3611-20211019T231647Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v3-00.13-00000000000-20211019T231647Z-avarab@gmail.com","subject":"[PATCH v3 13/13] run-command: remove old run_hook_{le,ve}() hook API","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-19T23:20:51Z","receivedAt":"2021-10-19T23:22:00Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nThe new hook.h library has replaced all run-command.h hook-related\nfunctionality. So let's delete this dead code.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n run-command.c | 32 --------------------------------\n run-command.h | 16 ----------------\n 2 files changed, 48 deletions(-)\n\ndiff --git a/run-command.c b/run-command.c\nindex d92e670c8ed..8a21ff525f3 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -1323,38 +1323,6 @@ int async_with_fork(void)\n #endif\n }\n \n-static 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 3fa7454cf8a..59e1fbff64c 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -224,22 +224,6 @@ int finish_command_in_signal(struct child_process *);\n  */\n int run_command(struct child_process *);\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-\n /*\n  * Trigger an auto-gc\n  */\n-- \n2.33.1.1338.g20da966911a\n\n"},{"id":"439978","messageId":"xmqqk0hwmldo.fsf@gitster.g","threadId":"56717","inReplyTo":"patch-v3-03.13-cbbfd77a4f6-20211019T231647Z-avarab@gmail.com","subject":"Re: [PATCH v3 03/13] rebase: convert pre-rebase to use hook.h","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-29T05:48:51Z","receivedAt":"2021-10-29T05:51:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:\n\n> From: Emily Shaffer <emilyshaffer@google.com>\n>\n> Move the pre-rebase hook away from run-command.h to and over to the\n> new hook.h library.\n>\n> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> ---\n>  builtin/rebase.c | 8 ++++++--\n>  1 file changed, 6 insertions(+), 2 deletions(-)\n>\n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index 34b4744e5f3..758b5dfabe2 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> @@ -1017,6 +1018,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \tint reschedule_failed_exec = -1;\n>  \tint allow_preemptive_ff = 1;\n>  \tint preserve_merges_selected = 0;\n> +\tstruct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;\n>  \tstruct option builtin_rebase_options[] = {\n>  \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n>  \t\t\t   N_(\"revision\"),\n> @@ -1711,9 +1713,11 @@ 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_push(&hook_opt.args, options.upstream_arg);\n> +\tif (argc)\n> +\t\tstrvec_push(&hook_opt.args, argv[0]);\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_oneshot(\"pre-rebase\", &hook_opt))\n>  \t\tdie(_(\"The pre-rebase hook refused to rebase.\"));\n>  \n>  \tif (options.flags & REBASE_DIFFSTAT) {\n\n\nOK, so the convention is\n\n * if there is no extra args the hook takes, you only give hook_name\n   and NULL;\n\n * if there is even one arg the hook takes, you have to prepare the\n   opt struct and strvec_push() the arg yourself.\n\nThat would work, and it might be merely the matter of taste, but it\nwould have been nicer if the calling sequence to run_hooks_oneshot()\nwere more like run_hook_le().\n\nBut patches were already written, so let's keep reading.\n\n"},{"id":"439979","messageId":"xmqqfsskml2r.fsf@gitster.g","threadId":"56717","inReplyTo":"patch-v3-05.13-2a747a65829-20211019T231647Z-avarab@gmail.com","subject":"Re: [PATCH v3 05/13] hooks: convert 'post-checkout' hook to hook library","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-29T05:55:24Z","receivedAt":"2021-10-29T05:55:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:\n\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;\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> +\t\topt.absolute_path = 1;\n> +\n> +\t\tret = run_hooks_oneshot(\"post-checkout\", &opt);\n\nI can see how passing opt from the caller gives more flexibility\nlike allowing to pass arbitrary environments.  The interface still\nlooks a bit unwieldy as I expect passing command line args would be\na much more common need than tweaking the environment, but let's\nkeep reading.\n\n\n"},{"id":"439981","messageId":"xmqqbl38mkwa.fsf@gitster.g","threadId":"56717","inReplyTo":"patch-v3-07.13-840fb530df3-20211019T231647Z-avarab@gmail.com","subject":"Re: [PATCH v3 07/13] git hook run: add an --ignore-missing flag","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-29T05:59:17Z","receivedAt":"2021-10-29T05:59:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:\n\n> +\tint ignore_missing = 0;\n>  \tconst char *hook_name;\n>  \tconst char *hook_path;\n>  \tstruct option run_options[] = {\n> +\t\tOPT_BOOL(0, \"ignore-missing\", &ignore_missing,\n> +\t\t\t N_(\"silently ignore missing requested <hook-name>\")),\n>  \t\tOPT_END(),\n>  \t};\n>  \tint ret;\n> @@ -53,6 +56,8 @@ static int run(int argc, const char **argv, const char *prefix)\n>  \tgit_config(git_default_config, NULL);\n>  \n>  \thook_name = argv[0];\n> +\tif (ignore_missing)\n> +\t\treturn run_hooks_oneshot(hook_name, &opt);\n>  \thook_path = find_hook(hook_name);\n>  \tif (!hook_path) {\n>  \t\terror(\"cannot find a hook named %s\", hook_name);\n\nGiven that the rest of this \"run()\" function is more or less\nidentical to run_hooks_oneshot(), I have to wonder why we do not\ngive an .ignore_missing member to the hooks_opt structure to be done\nwith this.  Otehrwise, every time the underlying run_hooks()\ninterface needs to change, we need to update two places, no?\n\n"},{"id":"439982","messageId":"xmqq7ddwmjl7.fsf@gitster.g","threadId":"56717","inReplyTo":"cover-v3-00.13-00000000000-20211019T231647Z-avarab@gmail.com","subject":"Re: [PATCH v3 00/13] hook.[ch]: new library to run hooks + simple hook conversion","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-29T06:27:32Z","receivedAt":"2021-10-29T06:27:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:\n\n> Part 2 of the greater configurable hook saga, starting by converting\n> some existing simple hooks to the new hook.[ch] library and \"git hook\n> run\" utility.\n\nThese look mostly OK.  Other than .ignore_missing stuff, I think I\nagree with the direction of the overall series.\n\n"},{"id":"440226","messageId":"cover-v4-00.17-00000000000-20211101T184938Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v3-00.13-00000000000-20211019T231647Z-avarab@gmail.com","subject":"[PATCH v4 00/17] hook.[ch]: new library to run hooks + simple hook conversion","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-01T18:56:05Z","receivedAt":"2021-11-01T18:58:11Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Part 2 of the greater configurable hook saga, starting by converting\nsome existing simple hooks to the new hook.[ch] library and \"git hook\nrun\" utility. See [1] for the last iteration.\n\nThis re-roll should address Junio's comments on v3, i.e. there's now a\nrun_hooks_l() API similar to run_hook_le(). I also simplified the\ncallers of run_hooks(), which is now called run_hooks_opt().\n\nSince no callers needed anything but a one-shot call the memory public\nmemory management API is gone, that'll eventually be needed for a \"git\nhook list\" with configurable hooks, but let's introduce those API bits\nthen, not now.\n\nThis grew to 17 patches from 13 due to splitting up some commints for\nease of review, the overall diffstat got smaller. I picked a function\nwith the same width as the old function, so many of the diffs are\nnow simply a s/run_hook_le(NULL, /run_hooks_l(/.\n\n1. https://lore.kernel.org/git/cover-v3-00.13-00000000000-20211019T231647Z-avarab@gmail.com/\n\nEmily Shaffer (14):\n  hook: add 'run' subcommand\n  gc: use hook library for pre-auto-gc hook\n  am: convert {pre,post}-applypatch to use hook.h\n  rebase: convert pre-rebase to use hook.h\n  am: convert applypatch-msg to use hook.h\n  merge: convert post-merge to use hook.h\n  hooks: convert non-worktree 'post-checkout' hook to hook library\n  hooks: convert worktree 'post-checkout' hook to hook library\n  send-email: use 'git hook run' for 'sendemail-validate'\n  git-p4: use 'git hook' to run hooks\n  commit: convert {pre-commit,prepare-commit-msg} hook to hook.h\n  read-cache: convert post-index-change to use hook.h\n  receive-pack: convert push-to-checkout hook to hook.h\n  run-command: remove old run_hook_{le,ve}() hook API\n\nÆvar Arnfjörð Bjarmason (3):\n  hook API: add a run_hooks() wrapper\n  hook API: add a run_hooks_l() wrapper\n  git hook run: add an --ignore-missing flag\n\n .gitignore                 |   1 +\n Documentation/git-hook.txt |  45 +++++++++++++\n Documentation/githooks.txt |   4 ++\n Makefile                   |   1 +\n builtin.h                  |   1 +\n builtin/am.c               |   6 +-\n builtin/checkout.c         |   3 +-\n builtin/clone.c            |   3 +-\n builtin/gc.c               |   3 +-\n builtin/hook.c             |  84 +++++++++++++++++++++++\n builtin/merge.c            |   2 +-\n builtin/rebase.c           |   3 +-\n builtin/receive-pack.c     |   7 +-\n builtin/worktree.c         |  27 +++-----\n command-list.txt           |   1 +\n commit.c                   |  15 +++--\n git-p4.py                  |  70 ++-----------------\n git-send-email.perl        |  22 +++---\n git.c                      |   1 +\n hook.c                     | 131 ++++++++++++++++++++++++++++++++++++\n hook.h                     |  57 ++++++++++++++++\n read-cache.c               |   3 +-\n reset.c                    |   3 +-\n run-command.c              |  32 ---------\n run-command.h              |  17 -----\n t/t1800-hook.sh            | 134 +++++++++++++++++++++++++++++++++++++\n t/t9001-send-email.sh      |   4 +-\n 27 files changed, 522 insertions(+), 158 deletions(-)\n create mode 100644 Documentation/git-hook.txt\n create mode 100644 builtin/hook.c\n create mode 100755 t/t1800-hook.sh\n\nRange-diff against v3:\n 1:  02fd699e699 !  1:  d97d6734961 hook: add 'run' subcommand\n    @@ Commit message\n         builtin/{commit-graph,multi-pack-index}.c.\n     \n         Some of the implementation here, such as a function being named\n    -    run_hooks() when it's tasked with running one hook, to using the\n    +    run_hooks_opt() when it's tasked with running one hook, to using the\n         run_processes_parallel_tr2() API to run with jobs=1 is somewhere\n         between a bit odd and and an overkill for the current features of this\n         \"hook run\" command and the hook.[ch] API.\n    @@ builtin/hook.c (new)\n     +\tint i;\n     +\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n     +\tconst char *hook_name;\n    -+\tconst char *hook_path;\n     +\tstruct option run_options[] = {\n     +\t\tOPT_END(),\n     +\t};\n    @@ builtin/hook.c (new)\n     +\tgit_config(git_default_config, NULL);\n     +\n     +\thook_name = argv[0];\n    -+\thook_path = find_hook(hook_name);\n    -+\tif (!hook_path) {\n    -+\t\terror(\"cannot find a hook named %s\", hook_name);\n    -+\t\treturn 1;\n    -+\t}\n    -+\n    -+\tret = run_hooks(hook_name, hook_path, &opt);\n    -+\trun_hooks_opt_clear(&opt);\n    ++\topt.error_if_missing = 1;\n    ++\tret = run_hooks_opt(hook_name, &opt);\n    ++\tif (ret < 0) /* error() return */\n    ++\t\tret = 1;\n     +\treturn ret;\n     +usage:\n     +\tusage_with_options(builtin_hook_run_usage, run_options);\n    @@ hook.c: int hook_exists(const char *name)\n      \treturn !!find_hook(name);\n      }\n     +\n    -+void run_hooks_opt_clear(struct run_hooks_opt *o)\n    -+{\n    -+\tstrvec_clear(&o->env);\n    -+\tstrvec_clear(&o->args);\n    -+}\n    -+\n     +static int pick_next_hook(struct child_process *cp,\n     +\t\t\t  struct strbuf *out,\n     +\t\t\t  void *pp_cb,\n    @@ hook.c: int hook_exists(const char *name)\n     +\treturn 0;\n     +}\n     +\n    -+int run_hooks(const char *hook_name, const char *hook_path,\n    -+\t      struct run_hooks_opt *options)\n    ++static void run_hooks_opt_clear(struct run_hooks_opt *options)\n    ++{\n    ++\tstrvec_clear(&options->env);\n    ++\tstrvec_clear(&options->args);\n    ++}\n    ++\n    ++int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)\n     +{\n     +\tstruct hook_cb_data cb_data = {\n     +\t\t.rc = 0,\n     +\t\t.hook_name = hook_name,\n    -+\t\t.hook_path = hook_path,\n     +\t\t.options = options,\n     +\t};\n    ++\tconst char *const hook_path = find_hook(hook_name);\n     +\tint jobs = 1;\n    ++\tint ret = 0;\n     +\n     +\tif (!options)\n     +\t\tBUG(\"a struct run_hooks_opt must be provided to run_hooks\");\n     +\n    ++\tif (!hook_path && !options->error_if_missing)\n    ++\t\tgoto cleanup;\n    ++\n    ++\tif (!hook_path) {\n    ++\t\tret = error(\"cannot find a hook named %s\", hook_name);\n    ++\t\tgoto cleanup;\n    ++\t}\n    ++\n    ++\tcb_data.hook_path = hook_path;\n     +\trun_processes_parallel_tr2(jobs,\n     +\t\t\t\t   pick_next_hook,\n     +\t\t\t\t   notify_start_failure,\n    @@ hook.c: int hook_exists(const char *name)\n     +\t\t\t\t   &cb_data,\n     +\t\t\t\t   \"hook\",\n     +\t\t\t\t   hook_name);\n    -+\n    -+\treturn cb_data.rc;\n    ++\tret = cb_data.rc;\n    ++cleanup:\n    ++\trun_hooks_opt_clear(options);\n    ++\treturn ret;\n     +}\n     \n      ## hook.h ##\n    @@ hook.h\n     +\n     +\t/* Args to be passed to each hook */\n     +\tstruct strvec args;\n    ++\n    ++\t/* Emit an error if the hook is missing */\n    ++\tunsigned int error_if_missing:1;\n     +};\n     +\n     +#define RUN_HOOKS_OPT_INIT { \\\n    @@ hook.h: const char *find_hook(const char *name);\n      int hook_exists(const char *hookname);\n      \n     +/**\n    -+ * Clear data from an initialized \"struct run_hooks_opt\".\n    -+ */\n    -+void run_hooks_opt_clear(struct run_hooks_opt *o);\n    -+\n    -+/**\n    -+ * Takes an already resolved hook found via find_hook() and runs\n    -+ * it. Does not call run_hooks_opt_clear() for you.\n    ++ * Takes a `hook_name`, resolves it to a path with find_hook(), and\n    ++ * runs the hook for you with the options specified in \"struct\n    ++ * run_hooks opt\". Will free memory associated with the \"struct run_hooks_opt\".\n    ++ *\n    ++ * Returns the status code of the run hook, or a negative value on\n    ++ * error().\n     + */\n    -+int run_hooks(const char *hookname, const char *hook_path,\n    -+\t      struct run_hooks_opt *options);\n    ++int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);\n      #endif\n     \n      ## t/t1800-hook.sh (new) ##\n -:  ----------- >  2:  ca6464f7d5e hook API: add a run_hooks() wrapper\n 2:  42cc4d2c3c6 !  3:  173860afca1 gc: use hook library for pre-auto-gc hook\n    @@ Commit message\n         gc: use hook library for pre-auto-gc hook\n     \n         Move the pre-auto-gc hook away from run-command.h to and over to the\n    -    new hook.h library.\n    -\n    -    To do this introduce a simple run_hooks_oneshot() wrapper, we'll be\n    -    using it extensively for these simple cases of wanting to run a single\n    -    hook under a given name, and having it free the memory we allocate for\n    -    us.\n    +    new hook.h library. This uses the new run_hooks() wrapper.\n     \n         Signed-off-by: Emily Shaffer <emilyshaffer@google.com>\n         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n    @@ builtin/gc.c: static int need_to_gc(void)\n      \t\treturn 0;\n      \n     -\tif (run_hook_le(NULL, \"pre-auto-gc\", NULL))\n    -+\tif (run_hooks_oneshot(\"pre-auto-gc\", NULL))\n    ++\tif (run_hooks(\"pre-auto-gc\"))\n      \t\treturn 0;\n      \treturn 1;\n      }\n    -\n    - ## hook.c ##\n    -@@ hook.c: int run_hooks(const char *hook_name, const char *hook_path,\n    - \n    - \treturn cb_data.rc;\n    - }\n    -+\n    -+int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options)\n    -+{\n    -+\tconst char *hook_path;\n    -+\tstruct run_hooks_opt hook_opt_scratch = RUN_HOOKS_OPT_INIT;\n    -+\tint ret = 0;\n    -+\n    -+\tif (!options)\n    -+\t\toptions = &hook_opt_scratch;\n    -+\n    -+\thook_path = find_hook(hook_name);\n    -+\tif (!hook_path)\n    -+\t\tgoto cleanup;\n    -+\n    -+\tret = run_hooks(hook_name, hook_path, options);\n    -+cleanup:\n    -+\trun_hooks_opt_clear(options);\n    -+\n    -+\treturn ret;\n    -+}\n    -\n    - ## hook.h ##\n    -@@ hook.h: void run_hooks_opt_clear(struct run_hooks_opt *o);\n    - /**\n    -  * Takes an already resolved hook found via find_hook() and runs\n    -  * it. Does not call run_hooks_opt_clear() for you.\n    -+ *\n    -+ * See run_hooks_oneshot() for the simpler one-shot API.\n    -  */\n    - int run_hooks(const char *hookname, const char *hook_path,\n    - \t      struct run_hooks_opt *options);\n    -+\n    -+/**\n    -+ * Calls find_hook() on your \"hook_name\" and runs the hooks (if any)\n    -+ * with run_hooks().\n    -+ *\n    -+ * If \"options\" is provided calls run_hooks_opt_clear() on it for\n    -+ * you. If \"options\" is NULL the default options from\n    -+ * RUN_HOOKS_OPT_INIT will be used.\n    -+ */\n    -+int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options);\n    -+\n    - #endif\n 4:  b26cef24f39 !  4:  80a2171ddaf am: convert applypatch to use hook.h\n    @@ Metadata\n     Author: Emily Shaffer <emilyshaffer@google.com>\n     \n      ## Commit message ##\n    -    am: convert applypatch to use hook.h\n    +    am: convert {pre,post}-applypatch to use hook.h\n     \n    -    Teach pre-applypatch, post-applypatch, and applypatch-msg to use the\n    -    hook.h library instead of the run-command.h library.\n    +    Teach pre-applypatch and post-applypatch to use the hook.h library\n    +    instead of the run-command.h library.\n     \n         Signed-off-by: Emily Shaffer <emilyshaffer@google.com>\n         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n      ## builtin/am.c ##\n    -@@ builtin/am.c: 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;\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_oneshot(\"applypatch-msg\", &opt);\n    - \n    - \tif (!ret) {\n    - \t\tFREE_AND_NULL(state->msg);\n     @@ builtin/am.c: static void do_commit(const struct am_state *state)\n      \tconst char *reflog_msg, *author, *committer = NULL;\n      \tstruct strbuf sb = STRBUF_INIT;\n      \n     -\tif (run_hook_le(NULL, \"pre-applypatch\", NULL))\n    -+\tif (run_hooks_oneshot(\"pre-applypatch\", NULL))\n    ++\tif (run_hooks(\"pre-applypatch\"))\n      \t\texit(1);\n      \n      \tif (write_cache_as_tree(&tree, 0, NULL))\n    @@ builtin/am.c: static void do_commit(const struct am_state *state)\n      \t}\n      \n     -\trun_hook_le(NULL, \"post-applypatch\", NULL);\n    -+\trun_hooks_oneshot(\"post-applypatch\", NULL);\n    ++\trun_hooks(\"post-applypatch\");\n      \n      \tstrbuf_release(&sb);\n      }\n -:  ----------- >  5:  74f459db287 hook API: add a run_hooks_l() wrapper\n 3:  cbbfd77a4f6 !  6:  1fd70c0e88a rebase: convert pre-rebase to use hook.h\n    @@ Commit message\n         Move the pre-rebase hook away from run-command.h to and over to the\n         new hook.h library.\n     \n    +    Since this hook needs arguments introduce a run_hooksl() wrapper, like\n    +    run_hooks(), but it takes varargs.\n    +\n         Signed-off-by: Emily Shaffer <emilyshaffer@google.com>\n         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n    @@ builtin/rebase.c\n      #define DEFAULT_REFLOG_ACTION \"rebase\"\n      \n     @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)\n    - \tint reschedule_failed_exec = -1;\n    - \tint allow_preemptive_ff = 1;\n    - \tint preserve_merges_selected = 0;\n    -+\tstruct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;\n    - \tstruct option builtin_rebase_options[] = {\n    - \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n    - \t\t\t   N_(\"revision\"),\n    -@@ builtin/rebase.c: 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_push(&hook_opt.args, options.upstream_arg);\n    -+\tif (argc)\n    -+\t\tstrvec_push(&hook_opt.args, argv[0]);\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_oneshot(\"pre-rebase\", &hook_opt))\n    ++\t    run_hooks_l(\"pre-rebase\", options.upstream_arg,\n    + \t\t\targc ? argv[0] : NULL, NULL))\n      \t\tdie(_(\"The pre-rebase hook refused to rebase.\"));\n      \n    - \tif (options.flags & REBASE_DIFFSTAT) {\n -:  ----------- >  7:  ccba3ddf52e am: convert applypatch-msg to use hook.h\n 6:  7a9fd8627cd !  8:  2c23e8645ec merge: convert post-merge to use hook.h\n    @@ Commit message\n         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n      ## builtin/merge.c ##\n    -@@ builtin/merge.c: 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;\n    - \tconst struct object_id *head = &head_commit->object.oid;\n    - \n    - \tif (!msg)\n     @@ builtin/merge.c: 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_oneshot(\"post-merge\", &opt);\n    ++\trun_hooks_l(\"post-merge\", squash ? \"1\" : \"0\", NULL);\n      \n      \tapply_autostash(git_path_merge_autostash(the_repository));\n      \tstrbuf_release(&reflog_message);\n -:  ----------- >  9:  cb95c79093b hooks: convert non-worktree 'post-checkout' hook to hook library\n 5:  2a747a65829 ! 10:  f330600fec8 hooks: convert 'post-checkout' hook to hook library\n    @@ Metadata\n     Author: Emily Shaffer <emilyshaffer@google.com>\n     \n      ## Commit message ##\n    -    hooks: convert 'post-checkout' hook to hook library\n    +    hooks: convert worktree 'post-checkout' hook to hook library\n     \n         Move the running of the 'post-checkout' hook away from run-command.h\n    -    to the new hook.h library. For \"worktree\" this requires a change to it\n    -    to run the hooks from a given directory.\n    +    to the new hook.h library in builtin/worktree.c. For this special case\n    +    we need a change to the hook API to teach it to run the hook from a\n    +    given directory.\n     \n    -    We could strictly speaking skip the \"absolute_path\" flag and just\n    -    check if \"dir\" is specified, but let's split them up for clarity, as\n    -    well as for any future user who'd like to set \"dir\" but not implicitly\n    -    change the argument to an absolute path.\n    +    We cannot skip the \"absolute_path\" flag and just check if \"dir\" is\n    +    specified as we'd then fail to find our hook in the new dir we'd\n    +    chdir() to. We currently don't have a use-case for running a hook not\n    +    in our \"base\" repository at a given absolute path, so let's have \"dir\"\n    +    imply absolute_path(find_hook(hook_name)).\n     \n         Signed-off-by: Emily Shaffer <emilyshaffer@google.com>\n         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n    - ## builtin/checkout.c ##\n    -@@\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    -@@ builtin/checkout.c: 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;\n    -+\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    -+\treturn run_hooks_oneshot(\"post-checkout\", &opt);\n    - }\n    - \n    - static int update_some(const struct object_id *oid, struct strbuf *base,\n    -\n    - ## builtin/clone.c ##\n    -@@\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    -@@ builtin/clone.c: 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;\n    - \n    - \tif (option_no_checkout)\n    - \t\treturn 0;\n    -@@ builtin/clone.c: 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),\n    -+\t\t     \"1\", NULL);\n    -+\terr |= run_hooks_oneshot(\"post-checkout\", &hook_opt);\n    - \n    - \tif (!err && (option_recurse_submodules.nr > 0)) {\n    - \t\tstruct strvec args = STRVEC_INIT;\n    -\n      ## builtin/worktree.c ##\n     @@ builtin/worktree.c: 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    @@ builtin/worktree.c: static int add_worktree(const char *path, const char *refnam\n     +\t\t\t     \"1\",\n     +\t\t\t     NULL);\n     +\t\topt.dir = path;\n    -+\t\topt.absolute_path = 1;\n     +\n    -+\t\tret = run_hooks_oneshot(\"post-checkout\", &opt);\n    ++\t\tret = run_hooks_opt(\"post-checkout\", &opt);\n      \t}\n      \n      \tstrvec_clear(&child_env);\n    @@ hook.c: static int pick_next_hook(struct child_process *cp,\n      \n      \tstrvec_push(&cp->args, hook_path);\n      \tstrvec_pushv(&cp->args, hook_cb->options->args.v);\n    -@@ hook.c: static int notify_hook_finished(int result,\n    - int run_hooks(const char *hook_name, const char *hook_path,\n    - \t      struct run_hooks_opt *options)\n    +@@ hook.c: static void run_hooks_opt_clear(struct run_hooks_opt *options)\n    + \n    + int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)\n      {\n     +\tstruct strbuf abs_path = STRBUF_INIT;\n      \tstruct hook_cb_data cb_data = {\n      \t\t.rc = 0,\n      \t\t.hook_name = hook_name,\n    --\t\t.hook_path = hook_path,\n    - \t\t.options = options,\n    - \t};\n    - \tint jobs = 1;\n    -@@ hook.c: int run_hooks(const char *hook_name, const char *hook_path,\n    - \tif (!options)\n    - \t\tBUG(\"a struct run_hooks_opt must be provided to run_hooks\");\n    +@@ hook.c: int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)\n    + \t}\n      \n    -+\tif (options->absolute_path) {\n    + \tcb_data.hook_path = hook_path;\n    ++\tif (options->dir) {\n     +\t\tstrbuf_add_absolute_path(&abs_path, hook_path);\n    -+\t\thook_path = abs_path.buf;\n    ++\t\tcb_data.hook_path = abs_path.buf;\n     +\t}\n    -+\tcb_data.hook_path = hook_path;\n     +\n      \trun_processes_parallel_tr2(jobs,\n      \t\t\t\t   pick_next_hook,\n      \t\t\t\t   notify_start_failure,\n    -@@ hook.c: int run_hooks(const char *hook_name, const char *hook_path,\n    - \t\t\t\t   \"hook\",\n    +@@ hook.c: int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)\n      \t\t\t\t   hook_name);\n    - \n    -+\tif (options->absolute_path)\n    -+\t\tstrbuf_release(&abs_path);\n    -+\n    - \treturn cb_data.rc;\n    + \tret = cb_data.rc;\n    + cleanup:\n    ++\tstrbuf_release(&abs_path);\n    + \trun_hooks_opt_clear(options);\n    + \treturn ret;\n      }\n    - \n     \n      ## hook.h ##\n     @@ hook.h: struct run_hooks_opt\n      \n    - \t/* Args to be passed to each hook */\n    - \tstruct strvec args;\n    + \t/* Emit an error if the hook is missing */\n    + \tunsigned int error_if_missing:1;\n     +\n    -+\t/*\n    -+\t * Resolve and run the \"absolute_path(hook)\" instead of\n    -+\t * \"hook\". Used for \"git worktree\" hooks\n    ++\t/**\n    ++\t * An optional initial working directory for the hook,\n    ++\t * translates to \"struct child_process\"'s \"dir\" member.\n     +\t */\n    -+\tint absolute_path;\n    -+\n    -+\t/* Path to initial working directory for subprocess */\n     +\tconst char *dir;\n      };\n      \n      #define RUN_HOOKS_OPT_INIT { \\\n    -\n    - ## reset.c ##\n    -@@\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    -@@ reset.c: 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;\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_oneshot(\"post-checkout\", &opt);\n    -+\t}\n    - \n    - leave_reset_head:\n    - \tstrbuf_release(&msg);\n 7:  840fb530df3 ! 11:  a0b6818c766 git hook run: add an --ignore-missing flag\n    @@ Commit message\n         git hook run: add an --ignore-missing flag\n     \n         For certain one-shot hooks we'd like to optimistically run them, and\n    -    not complain if they don't exist. This will be used by send-email in a\n    -    subsequent commit.\n    +    not complain if they don't exist.\n    +\n    +    This was already supported by the underlying hook.c library, but had\n    +    not been exposed via \"git hook run\". The command version of this will\n    +    be used by send-email in a subsequent commit.\n     \n         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n    @@ builtin/hook.c: static int run(int argc, const char **argv, const char *prefix)\n      \tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n     +\tint ignore_missing = 0;\n      \tconst char *hook_name;\n    - \tconst char *hook_path;\n      \tstruct option run_options[] = {\n     +\t\tOPT_BOOL(0, \"ignore-missing\", &ignore_missing,\n     +\t\t\t N_(\"silently ignore missing requested <hook-name>\")),\n    @@ builtin/hook.c: static int run(int argc, const char **argv, const char *prefix)\n      \tgit_config(git_default_config, NULL);\n      \n      \thook_name = argv[0];\n    -+\tif (ignore_missing)\n    -+\t\treturn run_hooks_oneshot(hook_name, &opt);\n    - \thook_path = find_hook(hook_name);\n    - \tif (!hook_path) {\n    - \t\terror(\"cannot find a hook named %s\", hook_name);\n    +-\topt.error_if_missing = 1;\n    ++\tif (!ignore_missing)\n    ++\t\topt.error_if_missing = 1;\n    + \tret = run_hooks_opt(hook_name, &opt);\n    + \tif (ret < 0) /* error() return */\n    + \t\tret = 1;\n     \n      ## t/t1800-hook.sh ##\n     @@ t/t1800-hook.sh: test_expect_success 'git hook run: nonexistent hook' '\n 8:  716ebabd794 = 12:  efa35971e9f send-email: use 'git hook run' for 'sendemail-validate'\n 9:  95782109270 = 13:  98e0e3330fb git-p4: use 'git hook' to run hooks\n10:  706426c8a79 ! 14:  79ea5a2a4f5 commit: convert {pre-commit,prepare-commit-msg} hook to hook.h\n    @@ commit.c: size_t ignore_non_trailer(const char *buf, size_t len)\n     -\tstrvec_clear(&hook_env);\n      \n     -\treturn ret;\n    -+\treturn run_hooks_oneshot(name, &opt);\n    ++\treturn run_hooks_opt(name, &opt);\n      }\n11:  39069a9c3ff ! 15:  81612f94707 read-cache: convert post-index-change to use hook.h\n    @@ read-cache.c\n      \n      /* Mask for the name length in ce_flags in the on-disk index */\n      \n    -@@ read-cache.c: static int do_write_locked_index(struct index_state *istate, struct lock_file *l\n    - {\n    - \tint ret;\n    - \tint was_full = !istate->sparse_index;\n    -+\tstruct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;\n    - \n    - \tret = convert_to_sparse(istate, 0);\n    - \n     @@ read-cache.c: 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_oneshot(\"post-index-change\", &hook_opt);\n    -+\n    ++\trun_hooks_l(\"post-index-change\",\n    + \t\t\tistate->updated_workdir ? \"1\" : \"0\",\n    + \t\t\tistate->updated_skipworktree ? \"1\" : \"0\", NULL);\n      \tistate->updated_workdir = 0;\n    - \tistate->updated_skipworktree = 0;\n    - \n     \n      ## run-command.c ##\n     @@ run-command.c: int async_with_fork(void)\n12:  9818078f1e5 ! 16:  43ecd6697e0 receive-pack: convert push-to-checkout hook to hook.h\n    @@ builtin/receive-pack.c: static const char *push_to_checkout(unsigned char *hash,\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_oneshot(push_to_checkout_hook, &opt))\n    ++\tif (run_hooks_opt(push_to_checkout_hook, &opt))\n      \t\treturn \"push-to-checkout hook declined\";\n      \telse\n      \t\treturn NULL;\n13:  1bc080d3611 = 17:  9ef574fa30c run-command: remove old run_hook_{le,ve}() hook API\n-- \n2.33.1.1570.g069344fdd45\n\n"},{"id":"440227","messageId":"patch-v4-02.17-ca6464f7d5e-20211101T184938Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v4-00.17-00000000000-20211101T184938Z-avarab@gmail.com","subject":"[PATCH v4 02/17] hook API: add a run_hooks() wrapper","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-01T18:56:07Z","receivedAt":"2021-11-01T18:58:14Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add a run_hooks() wrapper, we'll use it in subsequent commits for the\nsimple cases of wanting to run a single hook under a given name,\nwithout providing options such as \"env\" or \"args\".\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n hook.c | 7 +++++++\n hook.h | 6 ++++++\n 2 files changed, 13 insertions(+)\n\ndiff --git a/hook.c b/hook.c\nindex 64d3608e45e..720994bf4f6 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -142,3 +142,10 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)\n \trun_hooks_opt_clear(options);\n \treturn ret;\n }\n+\n+int run_hooks(const char *hook_name)\n+{\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n+\n+\treturn run_hooks_opt(hook_name, &opt);\n+}\ndiff --git a/hook.h b/hook.h\nindex 782385cc235..9c358789958 100644\n--- a/hook.h\n+++ b/hook.h\n@@ -48,4 +48,10 @@ int hook_exists(const char *hookname);\n  * error().\n  */\n int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);\n+\n+/**\n+ * A wrapper for run_hooks_opt() which provides a dummy \"struct\n+ * run_hooks_opt\" initialized with \"RUN_HOOKS_OPT_INIT\".\n+ */\n+int run_hooks(const char *hook_name);\n #endif\n-- \n2.33.1.1570.g069344fdd45\n\n"},{"id":"440228","messageId":"patch-v4-09.17-cb95c79093b-20211101T184938Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v4-00.17-00000000000-20211101T184938Z-avarab@gmail.com","subject":"[PATCH v4 09/17] hooks: convert non-worktree 'post-checkout' hook to hook library","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-01T18:56:14Z","receivedAt":"2021-11-01T18:58:21Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the running of the 'post-checkout' hook away from run-command.h\nto the new hook.h library, except in the case of\nbuiltin/worktree.c. That special-case will be handled in a subsequent\ncommit.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/checkout.c | 3 ++-\n builtin/clone.c    | 3 ++-\n reset.c            | 3 ++-\n 3 files changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex cbf73b8c9f6..4af17d17217 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@@ -106,7 +107,7 @@ 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+\treturn run_hooks_l(\"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);\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex fb377b27657..ee27b9f8114 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@@ -705,7 +706,7 @@ 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+\terr |= run_hooks_l(\"post-checkout\", oid_to_hex(null_oid()),\n \t\t\t   oid_to_hex(&oid), \"1\", NULL);\n \n \tif (!err && (option_recurse_submodules.nr > 0)) {\ndiff --git a/reset.c b/reset.c\nindex f214df3d96c..0881e636915 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@@ -127,7 +128,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \t\t\t\t\t    reflog_head);\n \t}\n \tif (run_hook)\n-\t\trun_hook_le(NULL, \"post-checkout\",\n+\t\trun_hooks_l(\"post-checkout\",\n \t\t\t    oid_to_hex(orig ? orig : null_oid()),\n \t\t\t    oid_to_hex(oid), \"1\", NULL);\n \n-- \n2.33.1.1570.g069344fdd45\n\n"},{"id":"440229","messageId":"patch-v4-01.17-d97d6734961-20211101T184938Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v4-00.17-00000000000-20211101T184938Z-avarab@gmail.com","subject":"[PATCH v4 01/17] hook: add 'run' subcommand","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-01T18:56:06Z","receivedAt":"2021-11-01T18:58:41Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nIn order to enable hooks to be run as an external process, by a\nstandalone Git command, or by tools which wrap Git, provide an external\nmeans to run all configured hook commands for a given hook event.\n\nMost of our hooks require more complex functionality than this, but\nlet's start with the bare minimum required to support our simplest\nhooks.\n\nIn terms of implementation the usage_with_options() and \"goto usage\"\npattern here mirrors that of\nbuiltin/{commit-graph,multi-pack-index}.c.\n\nSome of the implementation here, such as a function being named\nrun_hooks_opt() when it's tasked with running one hook, to using the\nrun_processes_parallel_tr2() API to run with jobs=1 is somewhere\nbetween a bit odd and and an overkill for the current features of this\n\"hook run\" command and the hook.[ch] API.\n\nThis code will eventually be able to run multiple hooks declared in\nconfig in parallel, by starting out with these names and APIs we\nreduce the later churn of renaming functions, switching from the\nrun_command() to run_processes_parallel_tr2() API etc.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n .gitignore                 |   1 +\n Documentation/git-hook.txt |  37 +++++++++++\n Documentation/githooks.txt |   4 ++\n Makefile                   |   1 +\n builtin.h                  |   1 +\n builtin/hook.c             |  80 +++++++++++++++++++++++\n command-list.txt           |   1 +\n git.c                      |   1 +\n hook.c                     | 102 +++++++++++++++++++++++++++++\n hook.h                     |  35 ++++++++++\n t/t1800-hook.sh            | 129 +++++++++++++++++++++++++++++++++++++\n 11 files changed, 392 insertions(+)\n create mode 100644 Documentation/git-hook.txt\n create mode 100644 builtin/hook.c\n create mode 100755 t/t1800-hook.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex 054249b20a8..f817c509ec0 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -77,6 +77,7 @@\n /git-grep\n /git-hash-object\n /git-help\n+/git-hook\n /git-http-backend\n /git-http-fetch\n /git-http-push\ndiff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt\nnew file mode 100644\nindex 00000000000..e39b1b5d069\n--- /dev/null\n+++ b/Documentation/git-hook.txt\n@@ -0,0 +1,37 @@\n+git-hook(1)\n+===========\n+\n+NAME\n+----\n+git-hook - Run git hooks\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git hook' run <hook-name> [-- <hook-args>]\n+\n+DESCRIPTION\n+-----------\n+\n+A command interface to running git hooks (see linkgit:githooks[5]),\n+for use by other scripted git commands.\n+\n+SUBCOMMANDS\n+-----------\n+\n+run::\n+\tRun the `<hook-name>` hook. See linkgit:githooks[5] for\n+\tsupported hook names.\n++\n+\n+Any positional arguments to the hook should be passed after a\n+mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See\n+linkgit:githooks[5] for arguments hooks might expect (if any).\n+\n+SEE ALSO\n+--------\n+linkgit:githooks[5]\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex b51959ff941..a16e62bc8c8 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -698,6 +698,10 @@ 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+SEE ALSO\n+--------\n+linkgit:git-hook[1]\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex 12be39ac497..6cd20534304 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1107,6 +1107,7 @@ BUILTIN_OBJS += builtin/get-tar-commit-id.o\n BUILTIN_OBJS += builtin/grep.o\n BUILTIN_OBJS += builtin/hash-object.o\n BUILTIN_OBJS += builtin/help.o\n+BUILTIN_OBJS += builtin/hook.o\n BUILTIN_OBJS += builtin/index-pack.o\n BUILTIN_OBJS += builtin/init-db.o\n BUILTIN_OBJS += builtin/interpret-trailers.o\ndiff --git a/builtin.h b/builtin.h\nindex 8a58743ed63..83379f3832c 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -164,6 +164,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);\n int cmd_grep(int argc, const char **argv, const char *prefix);\n int cmd_hash_object(int argc, const char **argv, const char *prefix);\n int cmd_help(int argc, const char **argv, const char *prefix);\n+int cmd_hook(int argc, const char **argv, const char *prefix);\n int cmd_index_pack(int argc, const char **argv, const char *prefix);\n int cmd_init_db(int argc, const char **argv, const char *prefix);\n int cmd_interpret_trailers(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/hook.c b/builtin/hook.c\nnew file mode 100644\nindex 00000000000..9b67ff50cef\n--- /dev/null\n+++ b/builtin/hook.c\n@@ -0,0 +1,80 @@\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"config.h\"\n+#include \"hook.h\"\n+#include \"parse-options.h\"\n+#include \"strbuf.h\"\n+#include \"strvec.h\"\n+\n+#define BUILTIN_HOOK_RUN_USAGE \\\n+\tN_(\"git hook run <hook-name> [-- <hook-args>]\")\n+\n+static const char * const builtin_hook_usage[] = {\n+\tBUILTIN_HOOK_RUN_USAGE,\n+\tNULL\n+};\n+\n+static const char * const builtin_hook_run_usage[] = {\n+\tBUILTIN_HOOK_RUN_USAGE,\n+\tNULL\n+};\n+\n+static int run(int argc, const char **argv, const char *prefix)\n+{\n+\tint i;\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n+\tconst char *hook_name;\n+\tstruct option run_options[] = {\n+\t\tOPT_END(),\n+\t};\n+\tint ret;\n+\n+\targc = parse_options(argc, argv, prefix, run_options,\n+\t\t\t     builtin_hook_run_usage,\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n+\n+\tif (!argc)\n+\t\tgoto usage;\n+\n+\t/*\n+\t * Having a -- for \"run\" when providing <hook-args> is\n+\t * mandatory.\n+\t */\n+\tif (argc > 1 && strcmp(argv[1], \"--\") &&\n+\t    strcmp(argv[1], \"--end-of-options\"))\n+\t\tgoto usage;\n+\n+\t/* Add our arguments, start after -- */\n+\tfor (i = 2 ; i < argc; i++)\n+\t\tstrvec_push(&opt.args, argv[i]);\n+\n+\t/* Need to take into account core.hooksPath */\n+\tgit_config(git_default_config, NULL);\n+\n+\thook_name = argv[0];\n+\topt.error_if_missing = 1;\n+\tret = run_hooks_opt(hook_name, &opt);\n+\tif (ret < 0) /* error() return */\n+\t\tret = 1;\n+\treturn ret;\n+usage:\n+\tusage_with_options(builtin_hook_run_usage, run_options);\n+}\n+\n+int cmd_hook(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct option builtin_hook_options[] = {\n+\t\tOPT_END(),\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, builtin_hook_options,\n+\t\t\t     builtin_hook_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n+\tif (!argc)\n+\t\tgoto usage;\n+\n+\tif (!strcmp(argv[0], \"run\"))\n+\t\treturn run(argc, argv, prefix);\n+\n+usage:\n+\tusage_with_options(builtin_hook_usage, builtin_hook_options);\n+}\ndiff --git a/command-list.txt b/command-list.txt\nindex eb9cee8dee9..0c9af14bdf3 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -103,6 +103,7 @@ git-grep                                mainporcelain           info\n git-gui                                 mainporcelain\n git-hash-object                         plumbingmanipulators\n git-help                                ancillaryinterrogators          complete\n+git-hook                                purehelpers\n git-http-backend                        synchingrepositories\n git-http-fetch                          synchelpers\n git-http-push                           synchelpers\ndiff --git a/git.c b/git.c\nindex 5ff21be21f3..a5be02b04b8 100644\n--- a/git.c\n+++ b/git.c\n@@ -538,6 +538,7 @@ static struct cmd_struct commands[] = {\n \t{ \"grep\", cmd_grep, RUN_SETUP_GENTLY },\n \t{ \"hash-object\", cmd_hash_object },\n \t{ \"help\", cmd_help },\n+\t{ \"hook\", cmd_hook, RUN_SETUP },\n \t{ \"index-pack\", cmd_index_pack, RUN_SETUP_GENTLY | NO_PARSEOPT },\n \t{ \"init\", cmd_init_db },\n \t{ \"init-db\", cmd_init_db },\ndiff --git a/hook.c b/hook.c\nindex 55e1145a4b7..64d3608e45e 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -1,6 +1,7 @@\n #include \"cache.h\"\n #include \"hook.h\"\n #include \"run-command.h\"\n+#include \"config.h\"\n \n const char *find_hook(const char *name)\n {\n@@ -40,3 +41,104 @@ int hook_exists(const char *name)\n {\n \treturn !!find_hook(name);\n }\n+\n+static int pick_next_hook(struct child_process *cp,\n+\t\t\t  struct strbuf *out,\n+\t\t\t  void *pp_cb,\n+\t\t\t  void **pp_task_cb)\n+{\n+\tstruct hook_cb_data *hook_cb = pp_cb;\n+\tconst char *hook_path = hook_cb->hook_path;\n+\n+\tif (!hook_path)\n+\t\treturn 0;\n+\n+\tcp->no_stdin = 1;\n+\tcp->env = hook_cb->options->env.v;\n+\tcp->stdout_to_stderr = 1;\n+\tcp->trace2_hook_name = hook_cb->hook_name;\n+\n+\tstrvec_push(&cp->args, hook_path);\n+\tstrvec_pushv(&cp->args, hook_cb->options->args.v);\n+\n+\t/* Provide context for errors if necessary */\n+\t*pp_task_cb = (char *)hook_path;\n+\n+\t/*\n+\t * This pick_next_hook() will be called again, we're only\n+\t * running one hook, so indicate that no more work will be\n+\t * done.\n+\t */\n+\thook_cb->hook_path = NULL;\n+\n+\treturn 1;\n+}\n+\n+static int notify_start_failure(struct strbuf *out,\n+\t\t\t\tvoid *pp_cb,\n+\t\t\t\tvoid *pp_task_cp)\n+{\n+\tstruct hook_cb_data *hook_cb = pp_cb;\n+\tconst char *hook_path = pp_task_cp;\n+\n+\thook_cb->rc |= 1;\n+\n+\tstrbuf_addf(out, _(\"Couldn't start hook '%s'\\n\"),\n+\t\t    hook_path);\n+\n+\treturn 1;\n+}\n+\n+static int notify_hook_finished(int result,\n+\t\t\t\tstruct strbuf *out,\n+\t\t\t\tvoid *pp_cb,\n+\t\t\t\tvoid *pp_task_cb)\n+{\n+\tstruct hook_cb_data *hook_cb = pp_cb;\n+\n+\thook_cb->rc |= result;\n+\n+\treturn 0;\n+}\n+\n+static void run_hooks_opt_clear(struct run_hooks_opt *options)\n+{\n+\tstrvec_clear(&options->env);\n+\tstrvec_clear(&options->args);\n+}\n+\n+int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)\n+{\n+\tstruct hook_cb_data cb_data = {\n+\t\t.rc = 0,\n+\t\t.hook_name = hook_name,\n+\t\t.options = options,\n+\t};\n+\tconst char *const hook_path = find_hook(hook_name);\n+\tint jobs = 1;\n+\tint ret = 0;\n+\n+\tif (!options)\n+\t\tBUG(\"a struct run_hooks_opt must be provided to run_hooks\");\n+\n+\tif (!hook_path && !options->error_if_missing)\n+\t\tgoto cleanup;\n+\n+\tif (!hook_path) {\n+\t\tret = error(\"cannot find a hook named %s\", hook_name);\n+\t\tgoto cleanup;\n+\t}\n+\n+\tcb_data.hook_path = hook_path;\n+\trun_processes_parallel_tr2(jobs,\n+\t\t\t\t   pick_next_hook,\n+\t\t\t\t   notify_start_failure,\n+\t\t\t\t   notify_hook_finished,\n+\t\t\t\t   &cb_data,\n+\t\t\t\t   \"hook\",\n+\t\t\t\t   hook_name);\n+\tret = cb_data.rc;\n+cleanup:\n+\trun_hooks_opt_clear(options);\n+\treturn ret;\n+}\ndiff --git a/hook.h b/hook.h\nindex 6aa36fc7ff9..782385cc235 100644\n--- a/hook.h\n+++ b/hook.h\n@@ -1,5 +1,31 @@\n #ifndef HOOK_H\n #define HOOK_H\n+#include \"strvec.h\"\n+\n+struct run_hooks_opt\n+{\n+\t/* Environment vars to be set for each hook */\n+\tstruct strvec env;\n+\n+\t/* Args to be passed to each hook */\n+\tstruct strvec args;\n+\n+\t/* Emit an error if the hook is missing */\n+\tunsigned int error_if_missing:1;\n+};\n+\n+#define RUN_HOOKS_OPT_INIT { \\\n+\t.env = STRVEC_INIT, \\\n+\t.args = STRVEC_INIT, \\\n+}\n+\n+struct hook_cb_data {\n+\t/* rc reflects the cumulative failure state */\n+\tint rc;\n+\tconst char *hook_name;\n+\tconst char *hook_path;\n+\tstruct run_hooks_opt *options;\n+};\n \n /*\n  * Returns the path to the hook file, or NULL if the hook is missing\n@@ -13,4 +39,13 @@ const char *find_hook(const char *name);\n  */\n int hook_exists(const char *hookname);\n \n+/**\n+ * Takes a `hook_name`, resolves it to a path with find_hook(), and\n+ * runs the hook for you with the options specified in \"struct\n+ * run_hooks opt\". Will free memory associated with the \"struct run_hooks_opt\".\n+ *\n+ * Returns the status code of the run hook, or a negative value on\n+ * error().\n+ */\n+int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);\n #endif\ndiff --git a/t/t1800-hook.sh b/t/t1800-hook.sh\nnew file mode 100755\nindex 00000000000..3aea1b105f0\n--- /dev/null\n+++ b/t/t1800-hook.sh\n@@ -0,0 +1,129 @@\n+#!/bin/sh\n+\n+test_description='git-hook command'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n+. ./test-lib.sh\n+\n+test_expect_success 'git hook usage' '\n+\ttest_expect_code 129 git hook &&\n+\ttest_expect_code 129 git hook run &&\n+\ttest_expect_code 129 git hook run -h &&\n+\ttest_expect_code 129 git hook run --unknown 2>err &&\n+\tgrep \"unknown option\" err\n+'\n+\n+test_expect_success 'git hook run: nonexistent hook' '\n+\tcat >stderr.expect <<-\\EOF &&\n+\terror: cannot find a hook named test-hook\n+\tEOF\n+\ttest_expect_code 1 git hook run test-hook 2>stderr.actual &&\n+\ttest_cmp stderr.expect stderr.actual\n+'\n+\n+test_expect_success 'git hook run: basic' '\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\techo Test hook\n+\tEOF\n+\n+\tcat >expect <<-\\EOF &&\n+\tTest hook\n+\tEOF\n+\tgit hook run test-hook 2>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git hook run: stdout and stderr both write to our stderr' '\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\techo >&1 Will end up on stderr\n+\techo >&2 Will end up on stderr\n+\tEOF\n+\n+\tcat >stderr.expect <<-\\EOF &&\n+\tWill end up on stderr\n+\tWill end up on stderr\n+\tEOF\n+\tgit hook run test-hook >stdout.actual 2>stderr.actual &&\n+\ttest_cmp stderr.expect stderr.actual &&\n+\ttest_must_be_empty stdout.actual\n+'\n+\n+test_expect_success 'git hook run: exit codes are passed along' '\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\texit 1\n+\tEOF\n+\n+\ttest_expect_code 1 git hook run test-hook &&\n+\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\texit 2\n+\tEOF\n+\n+\ttest_expect_code 2 git hook run test-hook &&\n+\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\texit 128\n+\tEOF\n+\n+\ttest_expect_code 128 git hook run test-hook &&\n+\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\texit 129\n+\tEOF\n+\n+\ttest_expect_code 129 git hook run test-hook\n+'\n+\n+test_expect_success 'git hook run arg u ments without -- is not allowed' '\n+\ttest_expect_code 129 git hook run test-hook arg u ments\n+'\n+\n+test_expect_success 'git hook run -- pass arguments' '\n+\twrite_script .git/hooks/test-hook <<-\\EOF &&\n+\techo $1\n+\techo $2\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\targ\n+\tu ments\n+\tEOF\n+\n+\tgit hook run test-hook -- arg \"u ments\" 2>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git hook run -- out-of-repo runs excluded' '\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\techo Test hook\n+\tEOF\n+\n+\tnongit test_must_fail git hook run test-hook\n+'\n+\n+test_expect_success 'git -c core.hooksPath=<PATH> hook run' '\n+\tmkdir my-hooks &&\n+\twrite_script my-hooks/test-hook <<-\\EOF &&\n+\techo Hook ran $1 >>actual\n+\tEOF\n+\n+\tcat >expect <<-\\EOF &&\n+\tTest hook\n+\tHook ran one\n+\tHook ran two\n+\tHook ran three\n+\tHook ran four\n+\tEOF\n+\n+\t# Test various ways of specifying the path. See also\n+\t# t1350-config-hooks-path.sh\n+\t>actual &&\n+\tgit hook run test-hook -- ignored 2>>actual &&\n+\tgit -c core.hooksPath=my-hooks hook run test-hook -- one 2>>actual &&\n+\tgit -c core.hooksPath=my-hooks/ hook run test-hook -- two 2>>actual &&\n+\tgit -c core.hooksPath=\"$PWD/my-hooks\" hook run test-hook -- three 2>>actual &&\n+\tgit -c core.hooksPath=\"$PWD/my-hooks/\" hook run test-hook -- four 2>>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \n2.33.1.1570.g069344fdd45\n\n"},{"id":"440230","messageId":"patch-v4-05.17-74f459db287-20211101T184938Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v4-00.17-00000000000-20211101T184938Z-avarab@gmail.com","subject":"[PATCH v4 05/17] hook API: add a run_hooks_l() wrapper","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-01T18:56:10Z","receivedAt":"2021-11-01T18:58:55Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add a run_hooks_l() wrapper, we'll use it in subsequent commits for\nthe simple cases of wanting to run a single hook under a given name\nalong with a list of arguments.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n hook.c | 14 ++++++++++++++\n hook.h | 10 ++++++++++\n 2 files changed, 24 insertions(+)\n\ndiff --git a/hook.c b/hook.c\nindex 720994bf4f6..cf6d099f83a 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -149,3 +149,17 @@ int run_hooks(const char *hook_name)\n \n \treturn run_hooks_opt(hook_name, &opt);\n }\n+\n+int run_hooks_l(const char *hook_name, ...)\n+{\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n+\tva_list ap;\n+\tconst char *arg;\n+\n+\tva_start(ap, hook_name);\n+\twhile ((arg = va_arg(ap, const char *)))\n+\t\tstrvec_push(&opt.args, arg);\n+\tva_end(ap);\n+\n+\treturn run_hooks_opt(hook_name, &opt);\n+}\ndiff --git a/hook.h b/hook.h\nindex 9c358789958..54528395953 100644\n--- a/hook.h\n+++ b/hook.h\n@@ -54,4 +54,14 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);\n  * run_hooks_opt\" initialized with \"RUN_HOOKS_OPT_INIT\".\n  */\n int run_hooks(const char *hook_name);\n+\n+/**\n+ * Like run_hooks(), a wrapper for run_hooks_opt().\n+ *\n+ * In addition to the wrapping behavior provided by run_hooks(), this\n+ * wrapper takes a list of strings terminated by a NULL\n+ * argument. These things will be used as positional arguments to the\n+ * hook. This function behaves like the old run_hook_le() API.\n+ */\n+int run_hooks_l(const char *hook_name, ...);\n #endif\n-- \n2.33.1.1570.g069344fdd45\n\n"},{"id":"440231","messageId":"patch-v4-04.17-80a2171ddaf-20211101T184938Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v4-00.17-00000000000-20211101T184938Z-avarab@gmail.com","subject":"[PATCH v4 04/17] am: convert {pre,post}-applypatch to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-01T18:56:09Z","receivedAt":"2021-11-01T18:58:56Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nTeach pre-applypatch and post-applypatch to use the hook.h library\ninstead of the run-command.h library.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/am.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..4b334cb7b12 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1609,7 +1609,7 @@ static void do_commit(const struct am_state *state)\n \tconst char *reflog_msg, *author, *committer = NULL;\n \tstruct strbuf sb = STRBUF_INIT;\n \n-\tif (run_hook_le(NULL, \"pre-applypatch\", NULL))\n+\tif (run_hooks(\"pre-applypatch\"))\n \t\texit(1);\n \n \tif (write_cache_as_tree(&tree, 0, NULL))\n@@ -1661,7 +1661,7 @@ 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\");\n \n \tstrbuf_release(&sb);\n }\n-- \n2.33.1.1570.g069344fdd45\n\n"},{"id":"440232","messageId":"patch-v4-03.17-173860afca1-20211101T184938Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v4-00.17-00000000000-20211101T184938Z-avarab@gmail.com","subject":"[PATCH v4 03/17] gc: use hook library for pre-auto-gc hook","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-01T18:56:08Z","receivedAt":"2021-11-01T18:59:00Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the pre-auto-gc hook away from run-command.h to and over to the\nnew hook.h library. This uses the new run_hooks() wrapper.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/gc.c | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 6b3de3dd514..f57cbd2d217 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@@ -394,7 +395,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\"))\n \t\treturn 0;\n \treturn 1;\n }\n-- \n2.33.1.1570.g069344fdd45\n\n"},{"id":"440233","messageId":"patch-v4-07.17-ccba3ddf52e-20211101T184938Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v4-00.17-00000000000-20211101T184938Z-avarab@gmail.com","subject":"[PATCH v4 07/17] am: convert applypatch-msg to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-01T18:56:12Z","receivedAt":"2021-11-01T18:59:09Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nTeach applypatch-msg to use the hook.h library instead of the\nrun-command.h library.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/am.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 4b334cb7b12..ae0c484dcba 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -448,7 +448,7 @@ static int run_applypatch_msg_hook(struct am_state *state)\n \tint ret;\n \n \tassert(state->msg);\n-\tret = run_hook_le(NULL, \"applypatch-msg\", am_path(state, \"final-commit\"), NULL);\n+\tret = run_hooks_l(\"applypatch-msg\", am_path(state, \"final-commit\"), NULL);\n \n \tif (!ret) {\n \t\tFREE_AND_NULL(state->msg);\n-- \n2.33.1.1570.g069344fdd45\n\n"},{"id":"440234","messageId":"patch-v4-06.17-1fd70c0e88a-20211101T184938Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v4-00.17-00000000000-20211101T184938Z-avarab@gmail.com","subject":"[PATCH v4 06/17] rebase: convert pre-rebase to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-01T18:56:11Z","receivedAt":"2021-11-01T18:59:14Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the pre-rebase hook away from run-command.h to and over to the\nnew hook.h library.\n\nSince this hook needs arguments introduce a run_hooksl() wrapper, like\nrun_hooks(), but it takes varargs.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/rebase.c | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 34b4744e5f3..ac4120013a0 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@@ -1712,7 +1713,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t/* If a hook exists, give it a chance to interrupt*/\n \tif (!ok_to_skip_pre_rebase &&\n-\t    run_hook_le(NULL, \"pre-rebase\", options.upstream_arg,\n+\t    run_hooks_l(\"pre-rebase\", options.upstream_arg,\n \t\t\targc ? argv[0] : NULL, NULL))\n \t\tdie(_(\"The pre-rebase hook refused to rebase.\"));\n \n-- \n2.33.1.1570.g069344fdd45\n\n"},{"id":"440235","messageId":"patch-v4-10.17-f330600fec8-20211101T184938Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v4-00.17-00000000000-20211101T184938Z-avarab@gmail.com","subject":"[PATCH v4 10/17] hooks: convert worktree 'post-checkout' hook to hook library","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-01T18:56:15Z","receivedAt":"2021-11-01T18:59:15Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the running of the 'post-checkout' hook away from run-command.h\nto the new hook.h library in builtin/worktree.c. For this special case\nwe need a change to the hook API to teach it to run the hook from a\ngiven directory.\n\nWe cannot skip the \"absolute_path\" flag and just check if \"dir\" is\nspecified as we'd then fail to find our hook in the new dir we'd\nchdir() to. We currently don't have a use-case for running a hook not\nin our \"base\" repository at a given absolute path, so let's have \"dir\"\nimply absolute_path(find_hook(hook_name)).\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/worktree.c | 27 +++++++++++----------------\n hook.c             |  8 ++++++++\n hook.h             |  6 ++++++\n 3 files changed, 25 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex d22ece93e1a..4d9df5ecc4c 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -382,22 +382,17 @@ 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;\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_opt(\"post-checkout\", &opt);\n \t}\n \n \tstrvec_clear(&child_env);\ndiff --git a/hook.c b/hook.c\nindex cf6d099f83a..95f5b926354 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -57,6 +57,7 @@ static int pick_next_hook(struct child_process *cp,\n \tcp->env = hook_cb->options->env.v;\n \tcp->stdout_to_stderr = 1;\n \tcp->trace2_hook_name = hook_cb->hook_name;\n+\tcp->dir = hook_cb->options->dir;\n \n \tstrvec_push(&cp->args, hook_path);\n \tstrvec_pushv(&cp->args, hook_cb->options->args.v);\n@@ -109,6 +110,7 @@ static void run_hooks_opt_clear(struct run_hooks_opt *options)\n \n int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)\n {\n+\tstruct strbuf abs_path = STRBUF_INIT;\n \tstruct hook_cb_data cb_data = {\n \t\t.rc = 0,\n \t\t.hook_name = hook_name,\n@@ -130,6 +132,11 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)\n \t}\n \n \tcb_data.hook_path = hook_path;\n+\tif (options->dir) {\n+\t\tstrbuf_add_absolute_path(&abs_path, hook_path);\n+\t\tcb_data.hook_path = abs_path.buf;\n+\t}\n+\n \trun_processes_parallel_tr2(jobs,\n \t\t\t\t   pick_next_hook,\n \t\t\t\t   notify_start_failure,\n@@ -139,6 +146,7 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)\n \t\t\t\t   hook_name);\n \tret = cb_data.rc;\n cleanup:\n+\tstrbuf_release(&abs_path);\n \trun_hooks_opt_clear(options);\n \treturn ret;\n }\ndiff --git a/hook.h b/hook.h\nindex 54528395953..18d90aedf14 100644\n--- a/hook.h\n+++ b/hook.h\n@@ -12,6 +12,12 @@ struct run_hooks_opt\n \n \t/* Emit an error if the hook is missing */\n \tunsigned int error_if_missing:1;\n+\n+\t/**\n+\t * An optional initial working directory for the hook,\n+\t * translates to \"struct child_process\"'s \"dir\" member.\n+\t */\n+\tconst char *dir;\n };\n \n #define RUN_HOOKS_OPT_INIT { \\\n-- \n2.33.1.1570.g069344fdd45\n\n"},{"id":"440236","messageId":"patch-v4-08.17-2c23e8645ec-20211101T184938Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v4-00.17-00000000000-20211101T184938Z-avarab@gmail.com","subject":"[PATCH v4 08/17] merge: convert post-merge to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-01T18:56:13Z","receivedAt":"2021-11-01T18:59:24Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nTeach post-merge to use the hook.h library instead of the\nrun-command.h library to run hooks.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/merge.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex ea3112e0c0b..e6facd1c95d 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -488,7 +488,7 @@ 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+\trun_hooks_l(\"post-merge\", squash ? \"1\" : \"0\", NULL);\n \n \tapply_autostash(git_path_merge_autostash(the_repository));\n \tstrbuf_release(&reflog_message);\n-- \n2.33.1.1570.g069344fdd45\n\n"},{"id":"440237","messageId":"patch-v4-14.17-79ea5a2a4f5-20211101T184938Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v4-00.17-00000000000-20211101T184938Z-avarab@gmail.com","subject":"[PATCH v4 14/17] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-01T18:56:19Z","receivedAt":"2021-11-01T18:59:30Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove these hooks hook away from run-command.h to and over to the new\nhook.h library.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n commit.c | 15 ++++++++-------\n 1 file changed, 8 insertions(+), 7 deletions(-)\n\ndiff --git a/commit.c b/commit.c\nindex 551de4903c1..581d7dc216c 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@@ -1700,22 +1701,22 @@ size_t ignore_non_trailer(const char *buf, size_t len)\n int run_commit_hook(int editor_is_used, 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;\n \tva_list args;\n-\tint ret;\n+\tconst char *arg;\n \n-\tstrvec_pushf(&hook_env, \"GIT_INDEX_FILE=%s\", index_file);\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-\treturn ret;\n+\treturn run_hooks_opt(name, &opt);\n }\n-- \n2.33.1.1570.g069344fdd45\n\n"},{"id":"440238","messageId":"patch-v4-17.17-9ef574fa30c-20211101T184938Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v4-00.17-00000000000-20211101T184938Z-avarab@gmail.com","subject":"[PATCH v4 17/17] run-command: remove old run_hook_{le,ve}() hook API","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-01T18:56:22Z","receivedAt":"2021-11-01T18:59:36Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nThe new hook.h library has replaced all run-command.h hook-related\nfunctionality. So let's delete this dead code.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n run-command.c | 32 --------------------------------\n run-command.h | 16 ----------------\n 2 files changed, 48 deletions(-)\n\ndiff --git a/run-command.c b/run-command.c\nindex d92e670c8ed..8a21ff525f3 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -1323,38 +1323,6 @@ int async_with_fork(void)\n #endif\n }\n \n-static 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 3fa7454cf8a..59e1fbff64c 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -224,22 +224,6 @@ int finish_command_in_signal(struct child_process *);\n  */\n int run_command(struct child_process *);\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-\n /*\n  * Trigger an auto-gc\n  */\n-- \n2.33.1.1570.g069344fdd45\n\n"},{"id":"440239","messageId":"patch-v4-13.17-98e0e3330fb-20211101T184938Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v4-00.17-00000000000-20211101T184938Z-avarab@gmail.com","subject":"[PATCH v4 13/17] git-p4: use 'git hook' to run hooks","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-01T18:56:18Z","receivedAt":"2021-11-01T18:59:37Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nInstead of duplicating the behavior of run-command.h:run_hook_le() in\nPython, we can directly call 'git hook run'. We emulate the existence\ncheck with the --ignore-missing flag.\n\nWe're dropping the \"verbose\" handling added in 9f59ca4d6af (git-p4:\ncreate new function run_git_hook, 2020-02-11), those who want\ndiagnostic output about how hooks are run are now able to get that via\ne.g. the trace2 facility and GIT_TRACE=1.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-p4.py | 70 +++++--------------------------------------------------\n 1 file changed, 6 insertions(+), 64 deletions(-)\n\ndiff --git a/git-p4.py b/git-p4.py\nindex 2b4500226aa..3b54168eb4a 100755\n--- a/git-p4.py\n+++ b/git-p4.py\n@@ -208,70 +208,12 @@ 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-        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-\n+    args = ['git', 'hook', 'run', '--ignore-missing', cmd]\n+    if param:\n+        args.append(\"--\")\n+        for p in param:\n+            args.append(p)\n+    return subprocess.call(args) == 0\n \n def write_pipe(c, stdin):\n     if verbose:\n-- \n2.33.1.1570.g069344fdd45\n\n"},{"id":"440240","messageId":"patch-v4-15.17-81612f94707-20211101T184938Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v4-00.17-00000000000-20211101T184938Z-avarab@gmail.com","subject":"[PATCH v4 15/17] read-cache: convert post-index-change to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-01T18:56:20Z","receivedAt":"2021-11-01T18:59:41Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the post-index-change hook away from run-command.h to and over to\nthe new hook.h library.\n\nThis removes the last direct user of \"run_hook_ve()\" outside of\nrun-command.c (\"run_hook_le()\" still uses it). So we can make the\nfunction static now. A subsequent commit will remove this code\nentirely when \"run_hook_le()\" itself goes away.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n read-cache.c  | 3 ++-\n run-command.c | 2 +-\n run-command.h | 1 -\n 3 files changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex f3986596623..4352661549f 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -28,6 +28,7 @@\n #include \"sparse-index.h\"\n #include \"csum-file.h\"\n #include \"promisor-remote.h\"\n+#include \"hook.h\"\n \n /* Mask for the name length in ce_flags in the on-disk index */\n \n@@ -3129,7 +3130,7 @@ 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+\trun_hooks_l(\"post-index-change\",\n \t\t\tistate->updated_workdir ? \"1\" : \"0\",\n \t\t\tistate->updated_skipworktree ? \"1\" : \"0\", NULL);\n \tistate->updated_workdir = 0;\ndiff --git a/run-command.c b/run-command.c\nindex 7ef5cc712a9..d92e670c8ed 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -1323,7 +1323,7 @@ int async_with_fork(void)\n #endif\n }\n \n-int run_hook_ve(const char *const *env, const char *name, va_list args)\n+static 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;\ndiff --git a/run-command.h b/run-command.h\nindex 49878262584..3fa7454cf8a 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -239,7 +239,6 @@ int run_command(struct child_process *);\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-- \n2.33.1.1570.g069344fdd45\n\n"},{"id":"440241","messageId":"patch-v4-16.17-43ecd6697e0-20211101T184938Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v4-00.17-00000000000-20211101T184938Z-avarab@gmail.com","subject":"[PATCH v4 16/17] receive-pack: convert push-to-checkout hook to hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-01T18:56:21Z","receivedAt":"2021-11-01T18:59:44Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the push-to-checkout hook away from run-command.h to and over to\nthe new hook.h library.\n\nThis removes the last direct user of run_hook_le(), so we could remove\nthat function now, but let's leave that to a follow-up cleanup commit.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/receive-pack.c | 7 +++++--\n 1 file changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 49b846d9605..5d6d8dd9a26 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1439,9 +1439,12 @@ 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;\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_opt(push_to_checkout_hook, &opt))\n \t\treturn \"push-to-checkout hook declined\";\n \telse\n \t\treturn NULL;\n-- \n2.33.1.1570.g069344fdd45\n\n"},{"id":"440242","messageId":"patch-v4-12.17-efa35971e9f-20211101T184938Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v4-00.17-00000000000-20211101T184938Z-avarab@gmail.com","subject":"[PATCH v4 12/17] send-email: use 'git hook run' for 'sendemail-validate'","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-01T18:56:17Z","receivedAt":"2021-11-01T18:59:49Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nChange the \"sendmail-validate\" hook to be run via the \"git hook run\"\nwrapper instead of via a direct invocation.\n\nThis is the smallest possibly change to get \"send-email\" using \"git\nhook run\". We still check the hook itself with \"-x\", and set a\n\"GIT_DIR\" variable, both of which are asserted by our tests. We'll\nneed to get rid of this special behavior if we start running N hooks,\nbut for now let's be as close to bug-for-bug compatible as possible.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl   | 22 ++++++++++++++--------\n t/t9001-send-email.sh |  4 ++--\n 2 files changed, 16 insertions(+), 10 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 5262d88ee32..4c20c0bbb79 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -195,13 +195,13 @@ sub format_2822_time {\n my $editor;\n \n sub system_or_msg {\n-\tmy ($args, $msg) = @_;\n+\tmy ($args, $msg, $cmd_name) = @_;\n \tsystem(@$args);\n \tmy $signalled = $? & 127;\n \tmy $exit_code = $? >> 8;\n \treturn unless $signalled or $exit_code;\n \n-\tmy @sprintf_args = ($args->[0], $exit_code);\n+\tmy @sprintf_args = ($cmd_name ? $cmd_name : $args->[0], $exit_code);\n \tif (defined $msg) {\n \t\t# Quiet the 'redundant' warning category, except we\n \t\t# need to support down to Perl 5.8, so we can't do a\n@@ -2039,10 +2039,10 @@ sub validate_patch {\n \tmy ($fn, $xfer_encoding) = @_;\n \n \tif ($repo) {\n+\t\tmy $hook_name = 'sendemail-validate';\n \t\tmy $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks');\n \t\trequire File::Spec;\n-\t\tmy $validate_hook = File::Spec->catfile($hooks_path,\n-\t\t\t\t\t    'sendemail-validate');\n+\t\tmy $validate_hook = File::Spec->catfile($hooks_path, $hook_name);\n \t\tmy $hook_error;\n \t\tif (-x $validate_hook) {\n \t\t\trequire Cwd;\n@@ -2052,13 +2052,19 @@ sub validate_patch {\n \t\t\tchdir($repo->wc_path() or $repo->repo_path())\n \t\t\t\tor die(\"chdir: $!\");\n \t\t\tlocal $ENV{\"GIT_DIR\"} = $repo->repo_path();\n-\t\t\t$hook_error = system_or_msg([$validate_hook, $target]);\n+\t\t\tmy @cmd = (\"git\", \"hook\", \"run\", \"--ignore-missing\",\n+\t\t\t\t    $hook_name, \"--\");\n+\t\t\tmy @cmd_msg = (@cmd, \"<patch>\");\n+\t\t\tmy @cmd_run = (@cmd, $target);\n+\t\t\t$hook_error = system_or_msg(\\@cmd_run, undef, \"@cmd_msg\");\n \t\t\tchdir($cwd_save) or die(\"chdir: $!\");\n \t\t}\n \t\tif ($hook_error) {\n-\t\t\tdie sprintf(__(\"fatal: %s: rejected by sendemail-validate hook\\n\" .\n-\t\t\t\t       \"%s\\n\" .\n-\t\t\t\t       \"warning: no patches were sent\\n\"), $fn, $hook_error);\n+\t\t\t$hook_error = sprintf(__(\"fatal: %s: rejected by %s hook\\n\" .\n+\t\t\t\t\t\t $hook_error . \"\\n\" .\n+\t\t\t\t\t\t \"warning: no patches were sent\\n\"),\n+\t\t\t\t\t      $fn, $hook_name);\n+\t\t\tdie $hook_error;\n \t\t}\n \t}\n \ndiff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\nindex aa0c20499ba..84d0f40d76a 100755\n--- a/t/t9001-send-email.sh\n+++ b/t/t9001-send-email.sh\n@@ -539,7 +539,7 @@ test_expect_success $PREREQ \"--validate respects relative core.hooksPath path\" '\n \ttest_path_is_file my-hooks.ran &&\n \tcat >expect <<-EOF &&\n \tfatal: longline.patch: rejected by sendemail-validate hook\n-\tfatal: command '\"'\"'my-hooks/sendemail-validate'\"'\"' died with exit code 1\n+\tfatal: command '\"'\"'git hook run --ignore-missing sendemail-validate -- <patch>'\"'\"' died with exit code 1\n \twarning: no patches were sent\n \tEOF\n \ttest_cmp expect actual\n@@ -558,7 +558,7 @@ test_expect_success $PREREQ \"--validate respects absolute core.hooksPath path\" '\n \ttest_path_is_file my-hooks.ran &&\n \tcat >expect <<-EOF &&\n \tfatal: longline.patch: rejected by sendemail-validate hook\n-\tfatal: command '\"'\"'$hooks_path/sendemail-validate'\"'\"' died with exit code 1\n+\tfatal: command '\"'\"'git hook run --ignore-missing sendemail-validate -- <patch>'\"'\"' died with exit code 1\n \twarning: no patches were sent\n \tEOF\n \ttest_cmp expect actual\n-- \n2.33.1.1570.g069344fdd45\n\n"},{"id":"440243","messageId":"patch-v4-11.17-a0b6818c766-20211101T184938Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v4-00.17-00000000000-20211101T184938Z-avarab@gmail.com","subject":"[PATCH v4 11/17] git hook run: add an --ignore-missing flag","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-01T18:56:16Z","receivedAt":"2021-11-01T18:59:50Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"For certain one-shot hooks we'd like to optimistically run them, and\nnot complain if they don't exist.\n\nThis was already supported by the underlying hook.c library, but had\nnot been exposed via \"git hook run\". The command version of this will\nbe used by send-email in a subsequent commit.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Documentation/git-hook.txt | 10 +++++++++-\n builtin/hook.c             |  8 ++++++--\n t/t1800-hook.sh            |  5 +++++\n 3 files changed, 20 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt\nindex e39b1b5d069..77c3a8ad909 100644\n--- a/Documentation/git-hook.txt\n+++ b/Documentation/git-hook.txt\n@@ -8,7 +8,7 @@ git-hook - Run git hooks\n SYNOPSIS\n --------\n [verse]\n-'git hook' run <hook-name> [-- <hook-args>]\n+'git hook' run [--ignore-missing] <hook-name> [-- <hook-args>]\n \n DESCRIPTION\n -----------\n@@ -28,6 +28,14 @@ Any positional arguments to the hook should be passed after a\n mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See\n linkgit:githooks[5] for arguments hooks might expect (if any).\n \n+OPTIONS\n+-------\n+\n+--ignore-missing::\n+\tIgnore any missing hook by quietly returning zero. Used for\n+\ttools that want to do a blind one-shot run of a hook that may\n+\tor may not be present.\n+\n SEE ALSO\n --------\n linkgit:githooks[5]\ndiff --git a/builtin/hook.c b/builtin/hook.c\nindex 9b67ff50cef..54e5c6ec933 100644\n--- a/builtin/hook.c\n+++ b/builtin/hook.c\n@@ -7,7 +7,7 @@\n #include \"strvec.h\"\n \n #define BUILTIN_HOOK_RUN_USAGE \\\n-\tN_(\"git hook run <hook-name> [-- <hook-args>]\")\n+\tN_(\"git hook run [--ignore-missing] <hook-name> [-- <hook-args>]\")\n \n static const char * const builtin_hook_usage[] = {\n \tBUILTIN_HOOK_RUN_USAGE,\n@@ -23,8 +23,11 @@ static int run(int argc, const char **argv, const char *prefix)\n {\n \tint i;\n \tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n+\tint ignore_missing = 0;\n \tconst char *hook_name;\n \tstruct option run_options[] = {\n+\t\tOPT_BOOL(0, \"ignore-missing\", &ignore_missing,\n+\t\t\t N_(\"silently ignore missing requested <hook-name>\")),\n \t\tOPT_END(),\n \t};\n \tint ret;\n@@ -52,7 +55,8 @@ static int run(int argc, const char **argv, const char *prefix)\n \tgit_config(git_default_config, NULL);\n \n \thook_name = argv[0];\n-\topt.error_if_missing = 1;\n+\tif (!ignore_missing)\n+\t\topt.error_if_missing = 1;\n \tret = run_hooks_opt(hook_name, &opt);\n \tif (ret < 0) /* error() return */\n \t\tret = 1;\ndiff --git a/t/t1800-hook.sh b/t/t1800-hook.sh\nindex 3aea1b105f0..29718aa9913 100755\n--- a/t/t1800-hook.sh\n+++ b/t/t1800-hook.sh\n@@ -21,6 +21,11 @@ test_expect_success 'git hook run: nonexistent hook' '\n \ttest_cmp stderr.expect stderr.actual\n '\n \n+test_expect_success 'git hook run: nonexistent hook with --ignore-missing' '\n+\tgit hook run --ignore-missing does-not-exist 2>stderr.actual &&\n+\ttest_must_be_empty stderr.actual\n+'\n+\n test_expect_success 'git hook run: basic' '\n \twrite_script .git/hooks/test-hook <<-EOF &&\n \techo Test hook\n-- \n2.33.1.1570.g069344fdd45\n\n"},{"id":"442110","messageId":"cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v4-00.17-00000000000-20211101T184938Z-avarab@gmail.com","subject":"[PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T11:45:59Z","receivedAt":"2021-11-23T11:46:23Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"A trivial update to the v4 of the series to migrate hook execution to\nthe new hook.c hook execution library. For a meaningful overall\nsummary see v4's CL[1].\n\nThe only update here is to stop using the \"env\" member of \"struct\nchild_process\" in favor of \"env_array\".\n\nThis is in preparation for a re-roll of another series[2] to remove\n\"argv\" (and soon in a re-roll, \"env\") from that API. Without this\nupdate the two would semantically conflict.\n\n1. https://lore.kernel.org/git/cover-v4-00.17-00000000000-20211101T184938Z-avarab@gmail.com/\n2. https://lore.kernel.org/git/cover-0.5-00000000000-20211122T153605Z-avarab@gmail.com/\n\nEmily Shaffer (14):\n  hook: add 'run' subcommand\n  gc: use hook library for pre-auto-gc hook\n  am: convert {pre,post}-applypatch to use hook.h\n  rebase: convert pre-rebase to use hook.h\n  am: convert applypatch-msg to use hook.h\n  merge: convert post-merge to use hook.h\n  hooks: convert non-worktree 'post-checkout' hook to hook library\n  hooks: convert worktree 'post-checkout' hook to hook library\n  send-email: use 'git hook run' for 'sendemail-validate'\n  git-p4: use 'git hook' to run hooks\n  commit: convert {pre-commit,prepare-commit-msg} hook to hook.h\n  read-cache: convert post-index-change to use hook.h\n  receive-pack: convert push-to-checkout hook to hook.h\n  run-command: remove old run_hook_{le,ve}() hook API\n\nÆvar Arnfjörð Bjarmason (3):\n  hook API: add a run_hooks() wrapper\n  hook API: add a run_hooks_l() wrapper\n  git hook run: add an --ignore-missing flag\n\n .gitignore                 |   1 +\n Documentation/git-hook.txt |  45 +++++++++++++\n Documentation/githooks.txt |   4 ++\n Makefile                   |   1 +\n builtin.h                  |   1 +\n builtin/am.c               |   6 +-\n builtin/checkout.c         |   3 +-\n builtin/clone.c            |   3 +-\n builtin/gc.c               |   3 +-\n builtin/hook.c             |  84 +++++++++++++++++++++++\n builtin/merge.c            |   2 +-\n builtin/rebase.c           |   3 +-\n builtin/receive-pack.c     |   7 +-\n builtin/worktree.c         |  27 +++-----\n command-list.txt           |   1 +\n commit.c                   |  15 +++--\n git-p4.py                  |  70 ++-----------------\n git-send-email.perl        |  22 +++---\n git.c                      |   1 +\n hook.c                     | 131 ++++++++++++++++++++++++++++++++++++\n hook.h                     |  57 ++++++++++++++++\n read-cache.c               |   3 +-\n reset.c                    |   3 +-\n run-command.c              |  32 ---------\n run-command.h              |  17 -----\n t/t1800-hook.sh            | 134 +++++++++++++++++++++++++++++++++++++\n t/t9001-send-email.sh      |   4 +-\n 27 files changed, 522 insertions(+), 158 deletions(-)\n create mode 100644 Documentation/git-hook.txt\n create mode 100644 builtin/hook.c\n create mode 100755 t/t1800-hook.sh\n\nRange-diff against v4:\n 1:  d97d6734961 !  1:  4ca52feebb8 hook: add 'run' subcommand\n    @@ hook.c: int hook_exists(const char *name)\n     +\t\treturn 0;\n     +\n     +\tcp->no_stdin = 1;\n    -+\tcp->env = hook_cb->options->env.v;\n    ++\tstrvec_pushv(&cp->env_array, hook_cb->options->env.v);\n     +\tcp->stdout_to_stderr = 1;\n     +\tcp->trace2_hook_name = hook_cb->hook_name;\n     +\n 2:  ca6464f7d5e =  2:  6275b97a306 hook API: add a run_hooks() wrapper\n 3:  173860afca1 =  3:  b5b3051b2e5 gc: use hook library for pre-auto-gc hook\n 4:  80a2171ddaf =  4:  c88eb5d4c25 am: convert {pre,post}-applypatch to use hook.h\n 5:  74f459db287 =  5:  1d8f7b7e4c1 hook API: add a run_hooks_l() wrapper\n 6:  1fd70c0e88a =  6:  d49a1444345 rebase: convert pre-rebase to use hook.h\n 7:  ccba3ddf52e =  7:  191fdad0165 am: convert applypatch-msg to use hook.h\n 8:  2c23e8645ec =  8:  119b92fbeae merge: convert post-merge to use hook.h\n 9:  cb95c79093b =  9:  359ba416e84 hooks: convert non-worktree 'post-checkout' hook to hook library\n10:  f330600fec8 ! 10:  b7599be95a7 hooks: convert worktree 'post-checkout' hook to hook library\n    @@ builtin/worktree.c: static int add_worktree(const char *path, const char *refnam\n     \n      ## hook.c ##\n     @@ hook.c: static int pick_next_hook(struct child_process *cp,\n    - \tcp->env = hook_cb->options->env.v;\n    + \tstrvec_pushv(&cp->env_array, hook_cb->options->env.v);\n      \tcp->stdout_to_stderr = 1;\n      \tcp->trace2_hook_name = hook_cb->hook_name;\n     +\tcp->dir = hook_cb->options->dir;\n11:  a0b6818c766 = 11:  f1c84d7f627 git hook run: add an --ignore-missing flag\n12:  efa35971e9f = 12:  4e0f94d9102 send-email: use 'git hook run' for 'sendemail-validate'\n13:  98e0e3330fb = 13:  e858f332a62 git-p4: use 'git hook' to run hooks\n14:  79ea5a2a4f5 = 14:  9a5956cc028 commit: convert {pre-commit,prepare-commit-msg} hook to hook.h\n15:  81612f94707 = 15:  6fd47c4c499 read-cache: convert post-index-change to use hook.h\n16:  43ecd6697e0 = 16:  b201ea46f4b receive-pack: convert push-to-checkout hook to hook.h\n17:  9ef574fa30c = 17:  281d17b04db run-command: remove old run_hook_{le,ve}() hook API\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442111","messageId":"patch-v5-01.17-4ca52feebb8-20211123T114206Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com","subject":"[PATCH v5 01/17] hook: add 'run' subcommand","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T11:46:00Z","receivedAt":"2021-11-23T11:46:24Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nIn order to enable hooks to be run as an external process, by a\nstandalone Git command, or by tools which wrap Git, provide an external\nmeans to run all configured hook commands for a given hook event.\n\nMost of our hooks require more complex functionality than this, but\nlet's start with the bare minimum required to support our simplest\nhooks.\n\nIn terms of implementation the usage_with_options() and \"goto usage\"\npattern here mirrors that of\nbuiltin/{commit-graph,multi-pack-index}.c.\n\nSome of the implementation here, such as a function being named\nrun_hooks_opt() when it's tasked with running one hook, to using the\nrun_processes_parallel_tr2() API to run with jobs=1 is somewhere\nbetween a bit odd and and an overkill for the current features of this\n\"hook run\" command and the hook.[ch] API.\n\nThis code will eventually be able to run multiple hooks declared in\nconfig in parallel, by starting out with these names and APIs we\nreduce the later churn of renaming functions, switching from the\nrun_command() to run_processes_parallel_tr2() API etc.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n .gitignore                 |   1 +\n Documentation/git-hook.txt |  37 +++++++++++\n Documentation/githooks.txt |   4 ++\n Makefile                   |   1 +\n builtin.h                  |   1 +\n builtin/hook.c             |  80 +++++++++++++++++++++++\n command-list.txt           |   1 +\n git.c                      |   1 +\n hook.c                     | 102 +++++++++++++++++++++++++++++\n hook.h                     |  35 ++++++++++\n t/t1800-hook.sh            | 129 +++++++++++++++++++++++++++++++++++++\n 11 files changed, 392 insertions(+)\n create mode 100644 Documentation/git-hook.txt\n create mode 100644 builtin/hook.c\n create mode 100755 t/t1800-hook.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex 054249b20a8..f817c509ec0 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -77,6 +77,7 @@\n /git-grep\n /git-hash-object\n /git-help\n+/git-hook\n /git-http-backend\n /git-http-fetch\n /git-http-push\ndiff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt\nnew file mode 100644\nindex 00000000000..e39b1b5d069\n--- /dev/null\n+++ b/Documentation/git-hook.txt\n@@ -0,0 +1,37 @@\n+git-hook(1)\n+===========\n+\n+NAME\n+----\n+git-hook - Run git hooks\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git hook' run <hook-name> [-- <hook-args>]\n+\n+DESCRIPTION\n+-----------\n+\n+A command interface to running git hooks (see linkgit:githooks[5]),\n+for use by other scripted git commands.\n+\n+SUBCOMMANDS\n+-----------\n+\n+run::\n+\tRun the `<hook-name>` hook. See linkgit:githooks[5] for\n+\tsupported hook names.\n++\n+\n+Any positional arguments to the hook should be passed after a\n+mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See\n+linkgit:githooks[5] for arguments hooks might expect (if any).\n+\n+SEE ALSO\n+--------\n+linkgit:githooks[5]\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex b51959ff941..a16e62bc8c8 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -698,6 +698,10 @@ 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+SEE ALSO\n+--------\n+linkgit:git-hook[1]\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex 12be39ac497..6cd20534304 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1107,6 +1107,7 @@ BUILTIN_OBJS += builtin/get-tar-commit-id.o\n BUILTIN_OBJS += builtin/grep.o\n BUILTIN_OBJS += builtin/hash-object.o\n BUILTIN_OBJS += builtin/help.o\n+BUILTIN_OBJS += builtin/hook.o\n BUILTIN_OBJS += builtin/index-pack.o\n BUILTIN_OBJS += builtin/init-db.o\n BUILTIN_OBJS += builtin/interpret-trailers.o\ndiff --git a/builtin.h b/builtin.h\nindex 8a58743ed63..83379f3832c 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -164,6 +164,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);\n int cmd_grep(int argc, const char **argv, const char *prefix);\n int cmd_hash_object(int argc, const char **argv, const char *prefix);\n int cmd_help(int argc, const char **argv, const char *prefix);\n+int cmd_hook(int argc, const char **argv, const char *prefix);\n int cmd_index_pack(int argc, const char **argv, const char *prefix);\n int cmd_init_db(int argc, const char **argv, const char *prefix);\n int cmd_interpret_trailers(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/hook.c b/builtin/hook.c\nnew file mode 100644\nindex 00000000000..9b67ff50cef\n--- /dev/null\n+++ b/builtin/hook.c\n@@ -0,0 +1,80 @@\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"config.h\"\n+#include \"hook.h\"\n+#include \"parse-options.h\"\n+#include \"strbuf.h\"\n+#include \"strvec.h\"\n+\n+#define BUILTIN_HOOK_RUN_USAGE \\\n+\tN_(\"git hook run <hook-name> [-- <hook-args>]\")\n+\n+static const char * const builtin_hook_usage[] = {\n+\tBUILTIN_HOOK_RUN_USAGE,\n+\tNULL\n+};\n+\n+static const char * const builtin_hook_run_usage[] = {\n+\tBUILTIN_HOOK_RUN_USAGE,\n+\tNULL\n+};\n+\n+static int run(int argc, const char **argv, const char *prefix)\n+{\n+\tint i;\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n+\tconst char *hook_name;\n+\tstruct option run_options[] = {\n+\t\tOPT_END(),\n+\t};\n+\tint ret;\n+\n+\targc = parse_options(argc, argv, prefix, run_options,\n+\t\t\t     builtin_hook_run_usage,\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n+\n+\tif (!argc)\n+\t\tgoto usage;\n+\n+\t/*\n+\t * Having a -- for \"run\" when providing <hook-args> is\n+\t * mandatory.\n+\t */\n+\tif (argc > 1 && strcmp(argv[1], \"--\") &&\n+\t    strcmp(argv[1], \"--end-of-options\"))\n+\t\tgoto usage;\n+\n+\t/* Add our arguments, start after -- */\n+\tfor (i = 2 ; i < argc; i++)\n+\t\tstrvec_push(&opt.args, argv[i]);\n+\n+\t/* Need to take into account core.hooksPath */\n+\tgit_config(git_default_config, NULL);\n+\n+\thook_name = argv[0];\n+\topt.error_if_missing = 1;\n+\tret = run_hooks_opt(hook_name, &opt);\n+\tif (ret < 0) /* error() return */\n+\t\tret = 1;\n+\treturn ret;\n+usage:\n+\tusage_with_options(builtin_hook_run_usage, run_options);\n+}\n+\n+int cmd_hook(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct option builtin_hook_options[] = {\n+\t\tOPT_END(),\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, builtin_hook_options,\n+\t\t\t     builtin_hook_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n+\tif (!argc)\n+\t\tgoto usage;\n+\n+\tif (!strcmp(argv[0], \"run\"))\n+\t\treturn run(argc, argv, prefix);\n+\n+usage:\n+\tusage_with_options(builtin_hook_usage, builtin_hook_options);\n+}\ndiff --git a/command-list.txt b/command-list.txt\nindex eb9cee8dee9..0c9af14bdf3 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -103,6 +103,7 @@ git-grep                                mainporcelain           info\n git-gui                                 mainporcelain\n git-hash-object                         plumbingmanipulators\n git-help                                ancillaryinterrogators          complete\n+git-hook                                purehelpers\n git-http-backend                        synchingrepositories\n git-http-fetch                          synchelpers\n git-http-push                           synchelpers\ndiff --git a/git.c b/git.c\nindex 5ff21be21f3..a5be02b04b8 100644\n--- a/git.c\n+++ b/git.c\n@@ -538,6 +538,7 @@ static struct cmd_struct commands[] = {\n \t{ \"grep\", cmd_grep, RUN_SETUP_GENTLY },\n \t{ \"hash-object\", cmd_hash_object },\n \t{ \"help\", cmd_help },\n+\t{ \"hook\", cmd_hook, RUN_SETUP },\n \t{ \"index-pack\", cmd_index_pack, RUN_SETUP_GENTLY | NO_PARSEOPT },\n \t{ \"init\", cmd_init_db },\n \t{ \"init-db\", cmd_init_db },\ndiff --git a/hook.c b/hook.c\nindex 55e1145a4b7..a0917cf877c 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -1,6 +1,7 @@\n #include \"cache.h\"\n #include \"hook.h\"\n #include \"run-command.h\"\n+#include \"config.h\"\n \n const char *find_hook(const char *name)\n {\n@@ -40,3 +41,104 @@ int hook_exists(const char *name)\n {\n \treturn !!find_hook(name);\n }\n+\n+static int pick_next_hook(struct child_process *cp,\n+\t\t\t  struct strbuf *out,\n+\t\t\t  void *pp_cb,\n+\t\t\t  void **pp_task_cb)\n+{\n+\tstruct hook_cb_data *hook_cb = pp_cb;\n+\tconst char *hook_path = hook_cb->hook_path;\n+\n+\tif (!hook_path)\n+\t\treturn 0;\n+\n+\tcp->no_stdin = 1;\n+\tstrvec_pushv(&cp->env_array, hook_cb->options->env.v);\n+\tcp->stdout_to_stderr = 1;\n+\tcp->trace2_hook_name = hook_cb->hook_name;\n+\n+\tstrvec_push(&cp->args, hook_path);\n+\tstrvec_pushv(&cp->args, hook_cb->options->args.v);\n+\n+\t/* Provide context for errors if necessary */\n+\t*pp_task_cb = (char *)hook_path;\n+\n+\t/*\n+\t * This pick_next_hook() will be called again, we're only\n+\t * running one hook, so indicate that no more work will be\n+\t * done.\n+\t */\n+\thook_cb->hook_path = NULL;\n+\n+\treturn 1;\n+}\n+\n+static int notify_start_failure(struct strbuf *out,\n+\t\t\t\tvoid *pp_cb,\n+\t\t\t\tvoid *pp_task_cp)\n+{\n+\tstruct hook_cb_data *hook_cb = pp_cb;\n+\tconst char *hook_path = pp_task_cp;\n+\n+\thook_cb->rc |= 1;\n+\n+\tstrbuf_addf(out, _(\"Couldn't start hook '%s'\\n\"),\n+\t\t    hook_path);\n+\n+\treturn 1;\n+}\n+\n+static int notify_hook_finished(int result,\n+\t\t\t\tstruct strbuf *out,\n+\t\t\t\tvoid *pp_cb,\n+\t\t\t\tvoid *pp_task_cb)\n+{\n+\tstruct hook_cb_data *hook_cb = pp_cb;\n+\n+\thook_cb->rc |= result;\n+\n+\treturn 0;\n+}\n+\n+static void run_hooks_opt_clear(struct run_hooks_opt *options)\n+{\n+\tstrvec_clear(&options->env);\n+\tstrvec_clear(&options->args);\n+}\n+\n+int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)\n+{\n+\tstruct hook_cb_data cb_data = {\n+\t\t.rc = 0,\n+\t\t.hook_name = hook_name,\n+\t\t.options = options,\n+\t};\n+\tconst char *const hook_path = find_hook(hook_name);\n+\tint jobs = 1;\n+\tint ret = 0;\n+\n+\tif (!options)\n+\t\tBUG(\"a struct run_hooks_opt must be provided to run_hooks\");\n+\n+\tif (!hook_path && !options->error_if_missing)\n+\t\tgoto cleanup;\n+\n+\tif (!hook_path) {\n+\t\tret = error(\"cannot find a hook named %s\", hook_name);\n+\t\tgoto cleanup;\n+\t}\n+\n+\tcb_data.hook_path = hook_path;\n+\trun_processes_parallel_tr2(jobs,\n+\t\t\t\t   pick_next_hook,\n+\t\t\t\t   notify_start_failure,\n+\t\t\t\t   notify_hook_finished,\n+\t\t\t\t   &cb_data,\n+\t\t\t\t   \"hook\",\n+\t\t\t\t   hook_name);\n+\tret = cb_data.rc;\n+cleanup:\n+\trun_hooks_opt_clear(options);\n+\treturn ret;\n+}\ndiff --git a/hook.h b/hook.h\nindex 6aa36fc7ff9..782385cc235 100644\n--- a/hook.h\n+++ b/hook.h\n@@ -1,5 +1,31 @@\n #ifndef HOOK_H\n #define HOOK_H\n+#include \"strvec.h\"\n+\n+struct run_hooks_opt\n+{\n+\t/* Environment vars to be set for each hook */\n+\tstruct strvec env;\n+\n+\t/* Args to be passed to each hook */\n+\tstruct strvec args;\n+\n+\t/* Emit an error if the hook is missing */\n+\tunsigned int error_if_missing:1;\n+};\n+\n+#define RUN_HOOKS_OPT_INIT { \\\n+\t.env = STRVEC_INIT, \\\n+\t.args = STRVEC_INIT, \\\n+}\n+\n+struct hook_cb_data {\n+\t/* rc reflects the cumulative failure state */\n+\tint rc;\n+\tconst char *hook_name;\n+\tconst char *hook_path;\n+\tstruct run_hooks_opt *options;\n+};\n \n /*\n  * Returns the path to the hook file, or NULL if the hook is missing\n@@ -13,4 +39,13 @@ const char *find_hook(const char *name);\n  */\n int hook_exists(const char *hookname);\n \n+/**\n+ * Takes a `hook_name`, resolves it to a path with find_hook(), and\n+ * runs the hook for you with the options specified in \"struct\n+ * run_hooks opt\". Will free memory associated with the \"struct run_hooks_opt\".\n+ *\n+ * Returns the status code of the run hook, or a negative value on\n+ * error().\n+ */\n+int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);\n #endif\ndiff --git a/t/t1800-hook.sh b/t/t1800-hook.sh\nnew file mode 100755\nindex 00000000000..3aea1b105f0\n--- /dev/null\n+++ b/t/t1800-hook.sh\n@@ -0,0 +1,129 @@\n+#!/bin/sh\n+\n+test_description='git-hook command'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n+. ./test-lib.sh\n+\n+test_expect_success 'git hook usage' '\n+\ttest_expect_code 129 git hook &&\n+\ttest_expect_code 129 git hook run &&\n+\ttest_expect_code 129 git hook run -h &&\n+\ttest_expect_code 129 git hook run --unknown 2>err &&\n+\tgrep \"unknown option\" err\n+'\n+\n+test_expect_success 'git hook run: nonexistent hook' '\n+\tcat >stderr.expect <<-\\EOF &&\n+\terror: cannot find a hook named test-hook\n+\tEOF\n+\ttest_expect_code 1 git hook run test-hook 2>stderr.actual &&\n+\ttest_cmp stderr.expect stderr.actual\n+'\n+\n+test_expect_success 'git hook run: basic' '\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\techo Test hook\n+\tEOF\n+\n+\tcat >expect <<-\\EOF &&\n+\tTest hook\n+\tEOF\n+\tgit hook run test-hook 2>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git hook run: stdout and stderr both write to our stderr' '\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\techo >&1 Will end up on stderr\n+\techo >&2 Will end up on stderr\n+\tEOF\n+\n+\tcat >stderr.expect <<-\\EOF &&\n+\tWill end up on stderr\n+\tWill end up on stderr\n+\tEOF\n+\tgit hook run test-hook >stdout.actual 2>stderr.actual &&\n+\ttest_cmp stderr.expect stderr.actual &&\n+\ttest_must_be_empty stdout.actual\n+'\n+\n+test_expect_success 'git hook run: exit codes are passed along' '\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\texit 1\n+\tEOF\n+\n+\ttest_expect_code 1 git hook run test-hook &&\n+\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\texit 2\n+\tEOF\n+\n+\ttest_expect_code 2 git hook run test-hook &&\n+\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\texit 128\n+\tEOF\n+\n+\ttest_expect_code 128 git hook run test-hook &&\n+\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\texit 129\n+\tEOF\n+\n+\ttest_expect_code 129 git hook run test-hook\n+'\n+\n+test_expect_success 'git hook run arg u ments without -- is not allowed' '\n+\ttest_expect_code 129 git hook run test-hook arg u ments\n+'\n+\n+test_expect_success 'git hook run -- pass arguments' '\n+\twrite_script .git/hooks/test-hook <<-\\EOF &&\n+\techo $1\n+\techo $2\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\targ\n+\tu ments\n+\tEOF\n+\n+\tgit hook run test-hook -- arg \"u ments\" 2>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git hook run -- out-of-repo runs excluded' '\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\techo Test hook\n+\tEOF\n+\n+\tnongit test_must_fail git hook run test-hook\n+'\n+\n+test_expect_success 'git -c core.hooksPath=<PATH> hook run' '\n+\tmkdir my-hooks &&\n+\twrite_script my-hooks/test-hook <<-\\EOF &&\n+\techo Hook ran $1 >>actual\n+\tEOF\n+\n+\tcat >expect <<-\\EOF &&\n+\tTest hook\n+\tHook ran one\n+\tHook ran two\n+\tHook ran three\n+\tHook ran four\n+\tEOF\n+\n+\t# Test various ways of specifying the path. See also\n+\t# t1350-config-hooks-path.sh\n+\t>actual &&\n+\tgit hook run test-hook -- ignored 2>>actual &&\n+\tgit -c core.hooksPath=my-hooks hook run test-hook -- one 2>>actual &&\n+\tgit -c core.hooksPath=my-hooks/ hook run test-hook -- two 2>>actual &&\n+\tgit -c core.hooksPath=\"$PWD/my-hooks\" hook run test-hook -- three 2>>actual &&\n+\tgit -c core.hooksPath=\"$PWD/my-hooks/\" hook run test-hook -- four 2>>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442113","messageId":"patch-v5-02.17-6275b97a306-20211123T114206Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com","subject":"[PATCH v5 02/17] hook API: add a run_hooks() wrapper","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T11:46:01Z","receivedAt":"2021-11-23T11:46:26Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add a run_hooks() wrapper, we'll use it in subsequent commits for the\nsimple cases of wanting to run a single hook under a given name,\nwithout providing options such as \"env\" or \"args\".\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n hook.c | 7 +++++++\n hook.h | 6 ++++++\n 2 files changed, 13 insertions(+)\n\ndiff --git a/hook.c b/hook.c\nindex a0917cf877c..d67a114e62d 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -142,3 +142,10 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)\n \trun_hooks_opt_clear(options);\n \treturn ret;\n }\n+\n+int run_hooks(const char *hook_name)\n+{\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n+\n+\treturn run_hooks_opt(hook_name, &opt);\n+}\ndiff --git a/hook.h b/hook.h\nindex 782385cc235..9c358789958 100644\n--- a/hook.h\n+++ b/hook.h\n@@ -48,4 +48,10 @@ int hook_exists(const char *hookname);\n  * error().\n  */\n int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);\n+\n+/**\n+ * A wrapper for run_hooks_opt() which provides a dummy \"struct\n+ * run_hooks_opt\" initialized with \"RUN_HOOKS_OPT_INIT\".\n+ */\n+int run_hooks(const char *hook_name);\n #endif\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442112","messageId":"patch-v5-03.17-b5b3051b2e5-20211123T114206Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com","subject":"[PATCH v5 03/17] gc: use hook library for pre-auto-gc hook","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T11:46:02Z","receivedAt":"2021-11-23T11:46:27Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the pre-auto-gc hook away from run-command.h to and over to the\nnew hook.h library. This uses the new run_hooks() wrapper.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/gc.c | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex bcef6a4c8da..4bbc58aae5b 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@@ -394,7 +395,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\"))\n \t\treturn 0;\n \treturn 1;\n }\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442114","messageId":"patch-v5-06.17-d49a1444345-20211123T114206Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com","subject":"[PATCH v5 06/17] rebase: convert pre-rebase to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T11:46:05Z","receivedAt":"2021-11-23T11:46:35Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the pre-rebase hook away from run-command.h to and over to the\nnew hook.h library.\n\nSince this hook needs arguments introduce a run_hooksl() wrapper, like\nrun_hooks(), but it takes varargs.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/rebase.c | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 34b4744e5f3..ac4120013a0 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@@ -1712,7 +1713,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t/* If a hook exists, give it a chance to interrupt*/\n \tif (!ok_to_skip_pre_rebase &&\n-\t    run_hook_le(NULL, \"pre-rebase\", options.upstream_arg,\n+\t    run_hooks_l(\"pre-rebase\", options.upstream_arg,\n \t\t\targc ? argv[0] : NULL, NULL))\n \t\tdie(_(\"The pre-rebase hook refused to rebase.\"));\n \n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442115","messageId":"patch-v5-04.17-c88eb5d4c25-20211123T114206Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com","subject":"[PATCH v5 04/17] am: convert {pre,post}-applypatch to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T11:46:03Z","receivedAt":"2021-11-23T11:46:38Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nTeach pre-applypatch and post-applypatch to use the hook.h library\ninstead of the run-command.h library.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/am.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..4b334cb7b12 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1609,7 +1609,7 @@ static void do_commit(const struct am_state *state)\n \tconst char *reflog_msg, *author, *committer = NULL;\n \tstruct strbuf sb = STRBUF_INIT;\n \n-\tif (run_hook_le(NULL, \"pre-applypatch\", NULL))\n+\tif (run_hooks(\"pre-applypatch\"))\n \t\texit(1);\n \n \tif (write_cache_as_tree(&tree, 0, NULL))\n@@ -1661,7 +1661,7 @@ 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\");\n \n \tstrbuf_release(&sb);\n }\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442116","messageId":"patch-v5-05.17-1d8f7b7e4c1-20211123T114206Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com","subject":"[PATCH v5 05/17] hook API: add a run_hooks_l() wrapper","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T11:46:04Z","receivedAt":"2021-11-23T11:46:38Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add a run_hooks_l() wrapper, we'll use it in subsequent commits for\nthe simple cases of wanting to run a single hook under a given name\nalong with a list of arguments.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n hook.c | 14 ++++++++++++++\n hook.h | 10 ++++++++++\n 2 files changed, 24 insertions(+)\n\ndiff --git a/hook.c b/hook.c\nindex d67a114e62d..1ad123422b2 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -149,3 +149,17 @@ int run_hooks(const char *hook_name)\n \n \treturn run_hooks_opt(hook_name, &opt);\n }\n+\n+int run_hooks_l(const char *hook_name, ...)\n+{\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n+\tva_list ap;\n+\tconst char *arg;\n+\n+\tva_start(ap, hook_name);\n+\twhile ((arg = va_arg(ap, const char *)))\n+\t\tstrvec_push(&opt.args, arg);\n+\tva_end(ap);\n+\n+\treturn run_hooks_opt(hook_name, &opt);\n+}\ndiff --git a/hook.h b/hook.h\nindex 9c358789958..54528395953 100644\n--- a/hook.h\n+++ b/hook.h\n@@ -54,4 +54,14 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);\n  * run_hooks_opt\" initialized with \"RUN_HOOKS_OPT_INIT\".\n  */\n int run_hooks(const char *hook_name);\n+\n+/**\n+ * Like run_hooks(), a wrapper for run_hooks_opt().\n+ *\n+ * In addition to the wrapping behavior provided by run_hooks(), this\n+ * wrapper takes a list of strings terminated by a NULL\n+ * argument. These things will be used as positional arguments to the\n+ * hook. This function behaves like the old run_hook_le() API.\n+ */\n+int run_hooks_l(const char *hook_name, ...);\n #endif\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442117","messageId":"patch-v5-08.17-119b92fbeae-20211123T114206Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com","subject":"[PATCH v5 08/17] merge: convert post-merge to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T11:46:07Z","receivedAt":"2021-11-23T11:46:39Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nTeach post-merge to use the hook.h library instead of the\nrun-command.h library to run hooks.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/merge.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex ea3112e0c0b..e6facd1c95d 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -488,7 +488,7 @@ 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+\trun_hooks_l(\"post-merge\", squash ? \"1\" : \"0\", NULL);\n \n \tapply_autostash(git_path_merge_autostash(the_repository));\n \tstrbuf_release(&reflog_message);\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442118","messageId":"patch-v5-07.17-191fdad0165-20211123T114206Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com","subject":"[PATCH v5 07/17] am: convert applypatch-msg to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T11:46:06Z","receivedAt":"2021-11-23T11:46:41Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nTeach applypatch-msg to use the hook.h library instead of the\nrun-command.h library.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/am.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 4b334cb7b12..ae0c484dcba 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -448,7 +448,7 @@ static int run_applypatch_msg_hook(struct am_state *state)\n \tint ret;\n \n \tassert(state->msg);\n-\tret = run_hook_le(NULL, \"applypatch-msg\", am_path(state, \"final-commit\"), NULL);\n+\tret = run_hooks_l(\"applypatch-msg\", am_path(state, \"final-commit\"), NULL);\n \n \tif (!ret) {\n \t\tFREE_AND_NULL(state->msg);\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442119","messageId":"patch-v5-10.17-b7599be95a7-20211123T114206Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com","subject":"[PATCH v5 10/17] hooks: convert worktree 'post-checkout' hook to hook library","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T11:46:09Z","receivedAt":"2021-11-23T11:46:43Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the running of the 'post-checkout' hook away from run-command.h\nto the new hook.h library in builtin/worktree.c. For this special case\nwe need a change to the hook API to teach it to run the hook from a\ngiven directory.\n\nWe cannot skip the \"absolute_path\" flag and just check if \"dir\" is\nspecified as we'd then fail to find our hook in the new dir we'd\nchdir() to. We currently don't have a use-case for running a hook not\nin our \"base\" repository at a given absolute path, so let's have \"dir\"\nimply absolute_path(find_hook(hook_name)).\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/worktree.c | 27 +++++++++++----------------\n hook.c             |  8 ++++++++\n hook.h             |  6 ++++++\n 3 files changed, 25 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex d22ece93e1a..4d9df5ecc4c 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -382,22 +382,17 @@ 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;\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_opt(\"post-checkout\", &opt);\n \t}\n \n \tstrvec_clear(&child_env);\ndiff --git a/hook.c b/hook.c\nindex 1ad123422b2..69a215b2c3c 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -57,6 +57,7 @@ static int pick_next_hook(struct child_process *cp,\n \tstrvec_pushv(&cp->env_array, hook_cb->options->env.v);\n \tcp->stdout_to_stderr = 1;\n \tcp->trace2_hook_name = hook_cb->hook_name;\n+\tcp->dir = hook_cb->options->dir;\n \n \tstrvec_push(&cp->args, hook_path);\n \tstrvec_pushv(&cp->args, hook_cb->options->args.v);\n@@ -109,6 +110,7 @@ static void run_hooks_opt_clear(struct run_hooks_opt *options)\n \n int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)\n {\n+\tstruct strbuf abs_path = STRBUF_INIT;\n \tstruct hook_cb_data cb_data = {\n \t\t.rc = 0,\n \t\t.hook_name = hook_name,\n@@ -130,6 +132,11 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)\n \t}\n \n \tcb_data.hook_path = hook_path;\n+\tif (options->dir) {\n+\t\tstrbuf_add_absolute_path(&abs_path, hook_path);\n+\t\tcb_data.hook_path = abs_path.buf;\n+\t}\n+\n \trun_processes_parallel_tr2(jobs,\n \t\t\t\t   pick_next_hook,\n \t\t\t\t   notify_start_failure,\n@@ -139,6 +146,7 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)\n \t\t\t\t   hook_name);\n \tret = cb_data.rc;\n cleanup:\n+\tstrbuf_release(&abs_path);\n \trun_hooks_opt_clear(options);\n \treturn ret;\n }\ndiff --git a/hook.h b/hook.h\nindex 54528395953..18d90aedf14 100644\n--- a/hook.h\n+++ b/hook.h\n@@ -12,6 +12,12 @@ struct run_hooks_opt\n \n \t/* Emit an error if the hook is missing */\n \tunsigned int error_if_missing:1;\n+\n+\t/**\n+\t * An optional initial working directory for the hook,\n+\t * translates to \"struct child_process\"'s \"dir\" member.\n+\t */\n+\tconst char *dir;\n };\n \n #define RUN_HOOKS_OPT_INIT { \\\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442120","messageId":"patch-v5-14.17-9a5956cc028-20211123T114206Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com","subject":"[PATCH v5 14/17] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T11:46:13Z","receivedAt":"2021-11-23T11:46:45Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove these hooks hook away from run-command.h to and over to the new\nhook.h library.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n commit.c | 15 ++++++++-------\n 1 file changed, 8 insertions(+), 7 deletions(-)\n\ndiff --git a/commit.c b/commit.c\nindex 551de4903c1..581d7dc216c 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@@ -1700,22 +1701,22 @@ size_t ignore_non_trailer(const char *buf, size_t len)\n int run_commit_hook(int editor_is_used, 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;\n \tva_list args;\n-\tint ret;\n+\tconst char *arg;\n \n-\tstrvec_pushf(&hook_env, \"GIT_INDEX_FILE=%s\", index_file);\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-\treturn ret;\n+\treturn run_hooks_opt(name, &opt);\n }\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442121","messageId":"patch-v5-13.17-e858f332a62-20211123T114206Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com","subject":"[PATCH v5 13/17] git-p4: use 'git hook' to run hooks","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T11:46:12Z","receivedAt":"2021-11-23T11:46:47Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nInstead of duplicating the behavior of run-command.h:run_hook_le() in\nPython, we can directly call 'git hook run'. We emulate the existence\ncheck with the --ignore-missing flag.\n\nWe're dropping the \"verbose\" handling added in 9f59ca4d6af (git-p4:\ncreate new function run_git_hook, 2020-02-11), those who want\ndiagnostic output about how hooks are run are now able to get that via\ne.g. the trace2 facility and GIT_TRACE=1.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-p4.py | 70 +++++--------------------------------------------------\n 1 file changed, 6 insertions(+), 64 deletions(-)\n\ndiff --git a/git-p4.py b/git-p4.py\nindex 2b4500226aa..3b54168eb4a 100755\n--- a/git-p4.py\n+++ b/git-p4.py\n@@ -208,70 +208,12 @@ 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-        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-\n+    args = ['git', 'hook', 'run', '--ignore-missing', cmd]\n+    if param:\n+        args.append(\"--\")\n+        for p in param:\n+            args.append(p)\n+    return subprocess.call(args) == 0\n \n def write_pipe(c, stdin):\n     if verbose:\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442122","messageId":"patch-v5-15.17-6fd47c4c499-20211123T114206Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com","subject":"[PATCH v5 15/17] read-cache: convert post-index-change to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T11:46:14Z","receivedAt":"2021-11-23T11:46:48Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the post-index-change hook away from run-command.h to and over to\nthe new hook.h library.\n\nThis removes the last direct user of \"run_hook_ve()\" outside of\nrun-command.c (\"run_hook_le()\" still uses it). So we can make the\nfunction static now. A subsequent commit will remove this code\nentirely when \"run_hook_le()\" itself goes away.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n read-cache.c  | 3 ++-\n run-command.c | 2 +-\n run-command.h | 1 -\n 3 files changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex f3986596623..4352661549f 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -28,6 +28,7 @@\n #include \"sparse-index.h\"\n #include \"csum-file.h\"\n #include \"promisor-remote.h\"\n+#include \"hook.h\"\n \n /* Mask for the name length in ce_flags in the on-disk index */\n \n@@ -3129,7 +3130,7 @@ 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+\trun_hooks_l(\"post-index-change\",\n \t\t\tistate->updated_workdir ? \"1\" : \"0\",\n \t\t\tistate->updated_skipworktree ? \"1\" : \"0\", NULL);\n \tistate->updated_workdir = 0;\ndiff --git a/run-command.c b/run-command.c\nindex f40df01c772..d7d64701ede 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -1323,7 +1323,7 @@ int async_with_fork(void)\n #endif\n }\n \n-int run_hook_ve(const char *const *env, const char *name, va_list args)\n+static 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;\ndiff --git a/run-command.h b/run-command.h\nindex 49878262584..3fa7454cf8a 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -239,7 +239,6 @@ int run_command(struct child_process *);\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-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442123","messageId":"patch-v5-16.17-b201ea46f4b-20211123T114206Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com","subject":"[PATCH v5 16/17] receive-pack: convert push-to-checkout hook to hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T11:46:15Z","receivedAt":"2021-11-23T11:46:50Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the push-to-checkout hook away from run-command.h to and over to\nthe new hook.h library.\n\nThis removes the last direct user of run_hook_le(), so we could remove\nthat function now, but let's leave that to a follow-up cleanup commit.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/receive-pack.c | 7 +++++--\n 1 file changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 49b846d9605..5d6d8dd9a26 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1439,9 +1439,12 @@ 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;\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_opt(push_to_checkout_hook, &opt))\n \t\treturn \"push-to-checkout hook declined\";\n \telse\n \t\treturn NULL;\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442124","messageId":"patch-v5-17.17-281d17b04db-20211123T114206Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com","subject":"[PATCH v5 17/17] run-command: remove old run_hook_{le,ve}() hook API","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T11:46:16Z","receivedAt":"2021-11-23T11:46:52Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nThe new hook.h library has replaced all run-command.h hook-related\nfunctionality. So let's delete this dead code.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n run-command.c | 32 --------------------------------\n run-command.h | 16 ----------------\n 2 files changed, 48 deletions(-)\n\ndiff --git a/run-command.c b/run-command.c\nindex d7d64701ede..7ecff26c1dc 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -1323,38 +1323,6 @@ int async_with_fork(void)\n #endif\n }\n \n-static 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 3fa7454cf8a..59e1fbff64c 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -224,22 +224,6 @@ int finish_command_in_signal(struct child_process *);\n  */\n int run_command(struct child_process *);\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-\n /*\n  * Trigger an auto-gc\n  */\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442125","messageId":"patch-v5-12.17-4e0f94d9102-20211123T114206Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com","subject":"[PATCH v5 12/17] send-email: use 'git hook run' for 'sendemail-validate'","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T11:46:11Z","receivedAt":"2021-11-23T11:46:54Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nChange the \"sendmail-validate\" hook to be run via the \"git hook run\"\nwrapper instead of via a direct invocation.\n\nThis is the smallest possibly change to get \"send-email\" using \"git\nhook run\". We still check the hook itself with \"-x\", and set a\n\"GIT_DIR\" variable, both of which are asserted by our tests. We'll\nneed to get rid of this special behavior if we start running N hooks,\nbut for now let's be as close to bug-for-bug compatible as possible.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl   | 22 ++++++++++++++--------\n t/t9001-send-email.sh |  4 ++--\n 2 files changed, 16 insertions(+), 10 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 5262d88ee32..4c20c0bbb79 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -195,13 +195,13 @@ sub format_2822_time {\n my $editor;\n \n sub system_or_msg {\n-\tmy ($args, $msg) = @_;\n+\tmy ($args, $msg, $cmd_name) = @_;\n \tsystem(@$args);\n \tmy $signalled = $? & 127;\n \tmy $exit_code = $? >> 8;\n \treturn unless $signalled or $exit_code;\n \n-\tmy @sprintf_args = ($args->[0], $exit_code);\n+\tmy @sprintf_args = ($cmd_name ? $cmd_name : $args->[0], $exit_code);\n \tif (defined $msg) {\n \t\t# Quiet the 'redundant' warning category, except we\n \t\t# need to support down to Perl 5.8, so we can't do a\n@@ -2039,10 +2039,10 @@ sub validate_patch {\n \tmy ($fn, $xfer_encoding) = @_;\n \n \tif ($repo) {\n+\t\tmy $hook_name = 'sendemail-validate';\n \t\tmy $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks');\n \t\trequire File::Spec;\n-\t\tmy $validate_hook = File::Spec->catfile($hooks_path,\n-\t\t\t\t\t    'sendemail-validate');\n+\t\tmy $validate_hook = File::Spec->catfile($hooks_path, $hook_name);\n \t\tmy $hook_error;\n \t\tif (-x $validate_hook) {\n \t\t\trequire Cwd;\n@@ -2052,13 +2052,19 @@ sub validate_patch {\n \t\t\tchdir($repo->wc_path() or $repo->repo_path())\n \t\t\t\tor die(\"chdir: $!\");\n \t\t\tlocal $ENV{\"GIT_DIR\"} = $repo->repo_path();\n-\t\t\t$hook_error = system_or_msg([$validate_hook, $target]);\n+\t\t\tmy @cmd = (\"git\", \"hook\", \"run\", \"--ignore-missing\",\n+\t\t\t\t    $hook_name, \"--\");\n+\t\t\tmy @cmd_msg = (@cmd, \"<patch>\");\n+\t\t\tmy @cmd_run = (@cmd, $target);\n+\t\t\t$hook_error = system_or_msg(\\@cmd_run, undef, \"@cmd_msg\");\n \t\t\tchdir($cwd_save) or die(\"chdir: $!\");\n \t\t}\n \t\tif ($hook_error) {\n-\t\t\tdie sprintf(__(\"fatal: %s: rejected by sendemail-validate hook\\n\" .\n-\t\t\t\t       \"%s\\n\" .\n-\t\t\t\t       \"warning: no patches were sent\\n\"), $fn, $hook_error);\n+\t\t\t$hook_error = sprintf(__(\"fatal: %s: rejected by %s hook\\n\" .\n+\t\t\t\t\t\t $hook_error . \"\\n\" .\n+\t\t\t\t\t\t \"warning: no patches were sent\\n\"),\n+\t\t\t\t\t      $fn, $hook_name);\n+\t\t\tdie $hook_error;\n \t\t}\n \t}\n \ndiff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\nindex aa0c20499ba..84d0f40d76a 100755\n--- a/t/t9001-send-email.sh\n+++ b/t/t9001-send-email.sh\n@@ -539,7 +539,7 @@ test_expect_success $PREREQ \"--validate respects relative core.hooksPath path\" '\n \ttest_path_is_file my-hooks.ran &&\n \tcat >expect <<-EOF &&\n \tfatal: longline.patch: rejected by sendemail-validate hook\n-\tfatal: command '\"'\"'my-hooks/sendemail-validate'\"'\"' died with exit code 1\n+\tfatal: command '\"'\"'git hook run --ignore-missing sendemail-validate -- <patch>'\"'\"' died with exit code 1\n \twarning: no patches were sent\n \tEOF\n \ttest_cmp expect actual\n@@ -558,7 +558,7 @@ test_expect_success $PREREQ \"--validate respects absolute core.hooksPath path\" '\n \ttest_path_is_file my-hooks.ran &&\n \tcat >expect <<-EOF &&\n \tfatal: longline.patch: rejected by sendemail-validate hook\n-\tfatal: command '\"'\"'$hooks_path/sendemail-validate'\"'\"' died with exit code 1\n+\tfatal: command '\"'\"'git hook run --ignore-missing sendemail-validate -- <patch>'\"'\"' died with exit code 1\n \twarning: no patches were sent\n \tEOF\n \ttest_cmp expect actual\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442126","messageId":"patch-v5-11.17-f1c84d7f627-20211123T114206Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com","subject":"[PATCH v5 11/17] git hook run: add an --ignore-missing flag","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T11:46:10Z","receivedAt":"2021-11-23T11:46:55Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"For certain one-shot hooks we'd like to optimistically run them, and\nnot complain if they don't exist.\n\nThis was already supported by the underlying hook.c library, but had\nnot been exposed via \"git hook run\". The command version of this will\nbe used by send-email in a subsequent commit.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Documentation/git-hook.txt | 10 +++++++++-\n builtin/hook.c             |  8 ++++++--\n t/t1800-hook.sh            |  5 +++++\n 3 files changed, 20 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt\nindex e39b1b5d069..77c3a8ad909 100644\n--- a/Documentation/git-hook.txt\n+++ b/Documentation/git-hook.txt\n@@ -8,7 +8,7 @@ git-hook - Run git hooks\n SYNOPSIS\n --------\n [verse]\n-'git hook' run <hook-name> [-- <hook-args>]\n+'git hook' run [--ignore-missing] <hook-name> [-- <hook-args>]\n \n DESCRIPTION\n -----------\n@@ -28,6 +28,14 @@ Any positional arguments to the hook should be passed after a\n mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See\n linkgit:githooks[5] for arguments hooks might expect (if any).\n \n+OPTIONS\n+-------\n+\n+--ignore-missing::\n+\tIgnore any missing hook by quietly returning zero. Used for\n+\ttools that want to do a blind one-shot run of a hook that may\n+\tor may not be present.\n+\n SEE ALSO\n --------\n linkgit:githooks[5]\ndiff --git a/builtin/hook.c b/builtin/hook.c\nindex 9b67ff50cef..54e5c6ec933 100644\n--- a/builtin/hook.c\n+++ b/builtin/hook.c\n@@ -7,7 +7,7 @@\n #include \"strvec.h\"\n \n #define BUILTIN_HOOK_RUN_USAGE \\\n-\tN_(\"git hook run <hook-name> [-- <hook-args>]\")\n+\tN_(\"git hook run [--ignore-missing] <hook-name> [-- <hook-args>]\")\n \n static const char * const builtin_hook_usage[] = {\n \tBUILTIN_HOOK_RUN_USAGE,\n@@ -23,8 +23,11 @@ static int run(int argc, const char **argv, const char *prefix)\n {\n \tint i;\n \tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n+\tint ignore_missing = 0;\n \tconst char *hook_name;\n \tstruct option run_options[] = {\n+\t\tOPT_BOOL(0, \"ignore-missing\", &ignore_missing,\n+\t\t\t N_(\"silently ignore missing requested <hook-name>\")),\n \t\tOPT_END(),\n \t};\n \tint ret;\n@@ -52,7 +55,8 @@ static int run(int argc, const char **argv, const char *prefix)\n \tgit_config(git_default_config, NULL);\n \n \thook_name = argv[0];\n-\topt.error_if_missing = 1;\n+\tif (!ignore_missing)\n+\t\topt.error_if_missing = 1;\n \tret = run_hooks_opt(hook_name, &opt);\n \tif (ret < 0) /* error() return */\n \t\tret = 1;\ndiff --git a/t/t1800-hook.sh b/t/t1800-hook.sh\nindex 3aea1b105f0..29718aa9913 100755\n--- a/t/t1800-hook.sh\n+++ b/t/t1800-hook.sh\n@@ -21,6 +21,11 @@ test_expect_success 'git hook run: nonexistent hook' '\n \ttest_cmp stderr.expect stderr.actual\n '\n \n+test_expect_success 'git hook run: nonexistent hook with --ignore-missing' '\n+\tgit hook run --ignore-missing does-not-exist 2>stderr.actual &&\n+\ttest_must_be_empty stderr.actual\n+'\n+\n test_expect_success 'git hook run: basic' '\n \twrite_script .git/hooks/test-hook <<-EOF &&\n \techo Test hook\n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"442127","messageId":"patch-v5-09.17-359ba416e84-20211123T114206Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com","subject":"[PATCH v5 09/17] hooks: convert non-worktree 'post-checkout' hook to hook library","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-23T11:46:08Z","receivedAt":"2021-11-23T11:46:56Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the running of the 'post-checkout' hook away from run-command.h\nto the new hook.h library, except in the case of\nbuiltin/worktree.c. That special-case will be handled in a subsequent\ncommit.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/checkout.c | 3 ++-\n builtin/clone.c    | 3 ++-\n reset.c            | 3 ++-\n 3 files changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex cbf73b8c9f6..4af17d17217 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@@ -106,7 +107,7 @@ 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+\treturn run_hooks_l(\"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);\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex fb377b27657..ee27b9f8114 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@@ -705,7 +706,7 @@ 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+\terr |= run_hooks_l(\"post-checkout\", oid_to_hex(null_oid()),\n \t\t\t   oid_to_hex(&oid), \"1\", NULL);\n \n \tif (!err && (option_recurse_submodules.nr > 0)) {\ndiff --git a/reset.c b/reset.c\nindex f214df3d96c..0881e636915 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@@ -127,7 +128,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \t\t\t\t\t    reflog_head);\n \t}\n \tif (run_hook)\n-\t\trun_hook_le(NULL, \"post-checkout\",\n+\t\trun_hooks_l(\"post-checkout\",\n \t\t\t    oid_to_hex(orig ? orig : null_oid()),\n \t\t\t    oid_to_hex(oid), \"1\", NULL);\n \n-- \n2.34.0.831.gd33babec0d1\n\n"},{"id":"444705","messageId":"cover-v6-00.17-00000000000-20211222T035755Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com","subject":"[PATCH v6 00/17] hook.[ch]: new library to run hooks + simple hook conversion","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-22T03:59:26Z","receivedAt":"2021-12-22T03:59:49Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"A trivial update to v5 to rebase it past conflicts with topics that\nrecently landed on \"master\". For v5 see:\nhttps://lore.kernel.org/git/cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com/\n\nEmily Shaffer (14):\n  hook: add 'run' subcommand\n  gc: use hook library for pre-auto-gc hook\n  am: convert {pre,post}-applypatch to use hook.h\n  rebase: convert pre-rebase to use hook.h\n  am: convert applypatch-msg to use hook.h\n  merge: convert post-merge to use hook.h\n  hooks: convert non-worktree 'post-checkout' hook to hook library\n  hooks: convert worktree 'post-checkout' hook to hook library\n  send-email: use 'git hook run' for 'sendemail-validate'\n  git-p4: use 'git hook' to run hooks\n  commit: convert {pre-commit,prepare-commit-msg} hook to hook.h\n  read-cache: convert post-index-change to use hook.h\n  receive-pack: convert push-to-checkout hook to hook.h\n  run-command: remove old run_hook_{le,ve}() hook API\n\nÆvar Arnfjörð Bjarmason (3):\n  hook API: add a run_hooks() wrapper\n  hook API: add a run_hooks_l() wrapper\n  git hook run: add an --ignore-missing flag\n\n .gitignore                 |   1 +\n Documentation/git-hook.txt |  45 +++++++++++++\n Documentation/githooks.txt |   4 ++\n Makefile                   |   1 +\n builtin.h                  |   1 +\n builtin/am.c               |   6 +-\n builtin/checkout.c         |   3 +-\n builtin/clone.c            |   3 +-\n builtin/gc.c               |   3 +-\n builtin/hook.c             |  84 +++++++++++++++++++++++\n builtin/merge.c            |   2 +-\n builtin/rebase.c           |   3 +-\n builtin/receive-pack.c     |   7 +-\n builtin/worktree.c         |  26 +++----\n command-list.txt           |   1 +\n commit.c                   |  15 +++--\n git-p4.py                  |  70 ++-----------------\n git-send-email.perl        |  22 +++---\n git.c                      |   1 +\n hook.c                     | 131 ++++++++++++++++++++++++++++++++++++\n hook.h                     |  57 ++++++++++++++++\n read-cache.c               |   3 +-\n reset.c                    |   3 +-\n run-command.c              |  33 ---------\n run-command.h              |  17 -----\n t/t1800-hook.sh            | 134 +++++++++++++++++++++++++++++++++++++\n t/t9001-send-email.sh      |   4 +-\n 27 files changed, 522 insertions(+), 158 deletions(-)\n create mode 100644 Documentation/git-hook.txt\n create mode 100644 builtin/hook.c\n create mode 100755 t/t1800-hook.sh\n\nRange-diff against v5:\n 1:  4ca52feebb8 =  1:  ba6fd47482e hook: add 'run' subcommand\n 2:  6275b97a306 =  2:  cfba5c139e7 hook API: add a run_hooks() wrapper\n 3:  b5b3051b2e5 =  3:  a4cca074bcb gc: use hook library for pre-auto-gc hook\n 4:  c88eb5d4c25 =  4:  ce57ce1adcb am: convert {pre,post}-applypatch to use hook.h\n 5:  1d8f7b7e4c1 =  5:  d6162fbef80 hook API: add a run_hooks_l() wrapper\n 6:  d49a1444345 =  6:  4c1a8951fc5 rebase: convert pre-rebase to use hook.h\n 7:  191fdad0165 =  7:  d8aa5e8345f am: convert applypatch-msg to use hook.h\n 8:  119b92fbeae =  8:  6f8d3754b4f merge: convert post-merge to use hook.h\n 9:  359ba416e84 !  9:  d3107034806 hooks: convert non-worktree 'post-checkout' hook to hook library\n    @@ builtin/checkout.c\n      #include \"ll-merge.h\"\n      #include \"lockfile.h\"\n      #include \"merge-recursive.h\"\n    -@@ builtin/checkout.c: struct branch_info {\n    +@@ builtin/checkout.c: static void branch_info_release(struct branch_info *info)\n      static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,\n      \t\t\t      int changed)\n      {\n10:  b7599be95a7 ! 10:  bff7c1513ca hooks: convert worktree 'post-checkout' hook to hook library\n    @@ builtin/worktree.c: static int add_worktree(const char *path, const char *refnam\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\tstruct child_process cp = CHILD_PROCESS_INIT;\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\tstrvec_pushv(&cp.env_array, env);\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()),\n11:  f1c84d7f627 = 11:  7d9c0a73568 git hook run: add an --ignore-missing flag\n12:  4e0f94d9102 = 12:  8ea3b250dff send-email: use 'git hook run' for 'sendemail-validate'\n13:  e858f332a62 = 13:  a184afd1ffd git-p4: use 'git hook' to run hooks\n14:  9a5956cc028 = 14:  1a43e50617f commit: convert {pre-commit,prepare-commit-msg} hook to hook.h\n15:  6fd47c4c499 = 15:  08b7e63ba5b read-cache: convert post-index-change to use hook.h\n16:  b201ea46f4b = 16:  c47b36ab41a receive-pack: convert push-to-checkout hook to hook.h\n17:  281d17b04db ! 17:  7b99a4b633c run-command: remove old run_hook_{le,ve}() hook API\n    @@ run-command.c: int async_with_fork(void)\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    +-\tif (env)\n    +-\t\tstrvec_pushv(&hook.env_array, (const char **)env);\n     -\thook.no_stdin = 1;\n     -\thook.stdout_to_stderr = 1;\n     -\thook.trace2_hook_name = name;\n-- \n2.34.1.1146.gb52885e7c44\n\n"},{"id":"444706","messageId":"patch-v6-01.17-ba6fd47482e-20211222T035755Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v6-00.17-00000000000-20211222T035755Z-avarab@gmail.com","subject":"[PATCH v6 01/17] hook: add 'run' subcommand","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-22T03:59:27Z","receivedAt":"2021-12-22T03:59:53Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nIn order to enable hooks to be run as an external process, by a\nstandalone Git command, or by tools which wrap Git, provide an external\nmeans to run all configured hook commands for a given hook event.\n\nMost of our hooks require more complex functionality than this, but\nlet's start with the bare minimum required to support our simplest\nhooks.\n\nIn terms of implementation the usage_with_options() and \"goto usage\"\npattern here mirrors that of\nbuiltin/{commit-graph,multi-pack-index}.c.\n\nSome of the implementation here, such as a function being named\nrun_hooks_opt() when it's tasked with running one hook, to using the\nrun_processes_parallel_tr2() API to run with jobs=1 is somewhere\nbetween a bit odd and and an overkill for the current features of this\n\"hook run\" command and the hook.[ch] API.\n\nThis code will eventually be able to run multiple hooks declared in\nconfig in parallel, by starting out with these names and APIs we\nreduce the later churn of renaming functions, switching from the\nrun_command() to run_processes_parallel_tr2() API etc.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n .gitignore                 |   1 +\n Documentation/git-hook.txt |  37 +++++++++++\n Documentation/githooks.txt |   4 ++\n Makefile                   |   1 +\n builtin.h                  |   1 +\n builtin/hook.c             |  80 +++++++++++++++++++++++\n command-list.txt           |   1 +\n git.c                      |   1 +\n hook.c                     | 102 +++++++++++++++++++++++++++++\n hook.h                     |  35 ++++++++++\n t/t1800-hook.sh            | 129 +++++++++++++++++++++++++++++++++++++\n 11 files changed, 392 insertions(+)\n create mode 100644 Documentation/git-hook.txt\n create mode 100644 builtin/hook.c\n create mode 100755 t/t1800-hook.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex 054249b20a8..f817c509ec0 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -77,6 +77,7 @@\n /git-grep\n /git-hash-object\n /git-help\n+/git-hook\n /git-http-backend\n /git-http-fetch\n /git-http-push\ndiff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt\nnew file mode 100644\nindex 00000000000..e39b1b5d069\n--- /dev/null\n+++ b/Documentation/git-hook.txt\n@@ -0,0 +1,37 @@\n+git-hook(1)\n+===========\n+\n+NAME\n+----\n+git-hook - Run git hooks\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git hook' run <hook-name> [-- <hook-args>]\n+\n+DESCRIPTION\n+-----------\n+\n+A command interface to running git hooks (see linkgit:githooks[5]),\n+for use by other scripted git commands.\n+\n+SUBCOMMANDS\n+-----------\n+\n+run::\n+\tRun the `<hook-name>` hook. See linkgit:githooks[5] for\n+\tsupported hook names.\n++\n+\n+Any positional arguments to the hook should be passed after a\n+mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See\n+linkgit:githooks[5] for arguments hooks might expect (if any).\n+\n+SEE ALSO\n+--------\n+linkgit:githooks[5]\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex b51959ff941..a16e62bc8c8 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -698,6 +698,10 @@ 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+SEE ALSO\n+--------\n+linkgit:git-hook[1]\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex 75ed168adbc..9a3cbc8c414 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1109,6 +1109,7 @@ BUILTIN_OBJS += builtin/get-tar-commit-id.o\n BUILTIN_OBJS += builtin/grep.o\n BUILTIN_OBJS += builtin/hash-object.o\n BUILTIN_OBJS += builtin/help.o\n+BUILTIN_OBJS += builtin/hook.o\n BUILTIN_OBJS += builtin/index-pack.o\n BUILTIN_OBJS += builtin/init-db.o\n BUILTIN_OBJS += builtin/interpret-trailers.o\ndiff --git a/builtin.h b/builtin.h\nindex 8a58743ed63..83379f3832c 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -164,6 +164,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);\n int cmd_grep(int argc, const char **argv, const char *prefix);\n int cmd_hash_object(int argc, const char **argv, const char *prefix);\n int cmd_help(int argc, const char **argv, const char *prefix);\n+int cmd_hook(int argc, const char **argv, const char *prefix);\n int cmd_index_pack(int argc, const char **argv, const char *prefix);\n int cmd_init_db(int argc, const char **argv, const char *prefix);\n int cmd_interpret_trailers(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/hook.c b/builtin/hook.c\nnew file mode 100644\nindex 00000000000..9b67ff50cef\n--- /dev/null\n+++ b/builtin/hook.c\n@@ -0,0 +1,80 @@\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"config.h\"\n+#include \"hook.h\"\n+#include \"parse-options.h\"\n+#include \"strbuf.h\"\n+#include \"strvec.h\"\n+\n+#define BUILTIN_HOOK_RUN_USAGE \\\n+\tN_(\"git hook run <hook-name> [-- <hook-args>]\")\n+\n+static const char * const builtin_hook_usage[] = {\n+\tBUILTIN_HOOK_RUN_USAGE,\n+\tNULL\n+};\n+\n+static const char * const builtin_hook_run_usage[] = {\n+\tBUILTIN_HOOK_RUN_USAGE,\n+\tNULL\n+};\n+\n+static int run(int argc, const char **argv, const char *prefix)\n+{\n+\tint i;\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n+\tconst char *hook_name;\n+\tstruct option run_options[] = {\n+\t\tOPT_END(),\n+\t};\n+\tint ret;\n+\n+\targc = parse_options(argc, argv, prefix, run_options,\n+\t\t\t     builtin_hook_run_usage,\n+\t\t\t     PARSE_OPT_KEEP_DASHDASH);\n+\n+\tif (!argc)\n+\t\tgoto usage;\n+\n+\t/*\n+\t * Having a -- for \"run\" when providing <hook-args> is\n+\t * mandatory.\n+\t */\n+\tif (argc > 1 && strcmp(argv[1], \"--\") &&\n+\t    strcmp(argv[1], \"--end-of-options\"))\n+\t\tgoto usage;\n+\n+\t/* Add our arguments, start after -- */\n+\tfor (i = 2 ; i < argc; i++)\n+\t\tstrvec_push(&opt.args, argv[i]);\n+\n+\t/* Need to take into account core.hooksPath */\n+\tgit_config(git_default_config, NULL);\n+\n+\thook_name = argv[0];\n+\topt.error_if_missing = 1;\n+\tret = run_hooks_opt(hook_name, &opt);\n+\tif (ret < 0) /* error() return */\n+\t\tret = 1;\n+\treturn ret;\n+usage:\n+\tusage_with_options(builtin_hook_run_usage, run_options);\n+}\n+\n+int cmd_hook(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct option builtin_hook_options[] = {\n+\t\tOPT_END(),\n+\t};\n+\n+\targc = parse_options(argc, argv, NULL, builtin_hook_options,\n+\t\t\t     builtin_hook_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n+\tif (!argc)\n+\t\tgoto usage;\n+\n+\tif (!strcmp(argv[0], \"run\"))\n+\t\treturn run(argc, argv, prefix);\n+\n+usage:\n+\tusage_with_options(builtin_hook_usage, builtin_hook_options);\n+}\ndiff --git a/command-list.txt b/command-list.txt\nindex 675c28f0bd0..9bd6f3c48f4 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -103,6 +103,7 @@ git-grep                                mainporcelain           info\n git-gui                                 mainporcelain\n git-hash-object                         plumbingmanipulators\n git-help                                ancillaryinterrogators          complete\n+git-hook                                purehelpers\n git-http-backend                        synchingrepositories\n git-http-fetch                          synchelpers\n git-http-push                           synchelpers\ndiff --git a/git.c b/git.c\nindex 7edafd8ecff..7030f1180b3 100644\n--- a/git.c\n+++ b/git.c\n@@ -541,6 +541,7 @@ static struct cmd_struct commands[] = {\n \t{ \"grep\", cmd_grep, RUN_SETUP_GENTLY },\n \t{ \"hash-object\", cmd_hash_object },\n \t{ \"help\", cmd_help },\n+\t{ \"hook\", cmd_hook, RUN_SETUP },\n \t{ \"index-pack\", cmd_index_pack, RUN_SETUP_GENTLY | NO_PARSEOPT },\n \t{ \"init\", cmd_init_db },\n \t{ \"init-db\", cmd_init_db },\ndiff --git a/hook.c b/hook.c\nindex 55e1145a4b7..a0917cf877c 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -1,6 +1,7 @@\n #include \"cache.h\"\n #include \"hook.h\"\n #include \"run-command.h\"\n+#include \"config.h\"\n \n const char *find_hook(const char *name)\n {\n@@ -40,3 +41,104 @@ int hook_exists(const char *name)\n {\n \treturn !!find_hook(name);\n }\n+\n+static int pick_next_hook(struct child_process *cp,\n+\t\t\t  struct strbuf *out,\n+\t\t\t  void *pp_cb,\n+\t\t\t  void **pp_task_cb)\n+{\n+\tstruct hook_cb_data *hook_cb = pp_cb;\n+\tconst char *hook_path = hook_cb->hook_path;\n+\n+\tif (!hook_path)\n+\t\treturn 0;\n+\n+\tcp->no_stdin = 1;\n+\tstrvec_pushv(&cp->env_array, hook_cb->options->env.v);\n+\tcp->stdout_to_stderr = 1;\n+\tcp->trace2_hook_name = hook_cb->hook_name;\n+\n+\tstrvec_push(&cp->args, hook_path);\n+\tstrvec_pushv(&cp->args, hook_cb->options->args.v);\n+\n+\t/* Provide context for errors if necessary */\n+\t*pp_task_cb = (char *)hook_path;\n+\n+\t/*\n+\t * This pick_next_hook() will be called again, we're only\n+\t * running one hook, so indicate that no more work will be\n+\t * done.\n+\t */\n+\thook_cb->hook_path = NULL;\n+\n+\treturn 1;\n+}\n+\n+static int notify_start_failure(struct strbuf *out,\n+\t\t\t\tvoid *pp_cb,\n+\t\t\t\tvoid *pp_task_cp)\n+{\n+\tstruct hook_cb_data *hook_cb = pp_cb;\n+\tconst char *hook_path = pp_task_cp;\n+\n+\thook_cb->rc |= 1;\n+\n+\tstrbuf_addf(out, _(\"Couldn't start hook '%s'\\n\"),\n+\t\t    hook_path);\n+\n+\treturn 1;\n+}\n+\n+static int notify_hook_finished(int result,\n+\t\t\t\tstruct strbuf *out,\n+\t\t\t\tvoid *pp_cb,\n+\t\t\t\tvoid *pp_task_cb)\n+{\n+\tstruct hook_cb_data *hook_cb = pp_cb;\n+\n+\thook_cb->rc |= result;\n+\n+\treturn 0;\n+}\n+\n+static void run_hooks_opt_clear(struct run_hooks_opt *options)\n+{\n+\tstrvec_clear(&options->env);\n+\tstrvec_clear(&options->args);\n+}\n+\n+int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)\n+{\n+\tstruct hook_cb_data cb_data = {\n+\t\t.rc = 0,\n+\t\t.hook_name = hook_name,\n+\t\t.options = options,\n+\t};\n+\tconst char *const hook_path = find_hook(hook_name);\n+\tint jobs = 1;\n+\tint ret = 0;\n+\n+\tif (!options)\n+\t\tBUG(\"a struct run_hooks_opt must be provided to run_hooks\");\n+\n+\tif (!hook_path && !options->error_if_missing)\n+\t\tgoto cleanup;\n+\n+\tif (!hook_path) {\n+\t\tret = error(\"cannot find a hook named %s\", hook_name);\n+\t\tgoto cleanup;\n+\t}\n+\n+\tcb_data.hook_path = hook_path;\n+\trun_processes_parallel_tr2(jobs,\n+\t\t\t\t   pick_next_hook,\n+\t\t\t\t   notify_start_failure,\n+\t\t\t\t   notify_hook_finished,\n+\t\t\t\t   &cb_data,\n+\t\t\t\t   \"hook\",\n+\t\t\t\t   hook_name);\n+\tret = cb_data.rc;\n+cleanup:\n+\trun_hooks_opt_clear(options);\n+\treturn ret;\n+}\ndiff --git a/hook.h b/hook.h\nindex 6aa36fc7ff9..782385cc235 100644\n--- a/hook.h\n+++ b/hook.h\n@@ -1,5 +1,31 @@\n #ifndef HOOK_H\n #define HOOK_H\n+#include \"strvec.h\"\n+\n+struct run_hooks_opt\n+{\n+\t/* Environment vars to be set for each hook */\n+\tstruct strvec env;\n+\n+\t/* Args to be passed to each hook */\n+\tstruct strvec args;\n+\n+\t/* Emit an error if the hook is missing */\n+\tunsigned int error_if_missing:1;\n+};\n+\n+#define RUN_HOOKS_OPT_INIT { \\\n+\t.env = STRVEC_INIT, \\\n+\t.args = STRVEC_INIT, \\\n+}\n+\n+struct hook_cb_data {\n+\t/* rc reflects the cumulative failure state */\n+\tint rc;\n+\tconst char *hook_name;\n+\tconst char *hook_path;\n+\tstruct run_hooks_opt *options;\n+};\n \n /*\n  * Returns the path to the hook file, or NULL if the hook is missing\n@@ -13,4 +39,13 @@ const char *find_hook(const char *name);\n  */\n int hook_exists(const char *hookname);\n \n+/**\n+ * Takes a `hook_name`, resolves it to a path with find_hook(), and\n+ * runs the hook for you with the options specified in \"struct\n+ * run_hooks opt\". Will free memory associated with the \"struct run_hooks_opt\".\n+ *\n+ * Returns the status code of the run hook, or a negative value on\n+ * error().\n+ */\n+int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);\n #endif\ndiff --git a/t/t1800-hook.sh b/t/t1800-hook.sh\nnew file mode 100755\nindex 00000000000..3aea1b105f0\n--- /dev/null\n+++ b/t/t1800-hook.sh\n@@ -0,0 +1,129 @@\n+#!/bin/sh\n+\n+test_description='git-hook command'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n+. ./test-lib.sh\n+\n+test_expect_success 'git hook usage' '\n+\ttest_expect_code 129 git hook &&\n+\ttest_expect_code 129 git hook run &&\n+\ttest_expect_code 129 git hook run -h &&\n+\ttest_expect_code 129 git hook run --unknown 2>err &&\n+\tgrep \"unknown option\" err\n+'\n+\n+test_expect_success 'git hook run: nonexistent hook' '\n+\tcat >stderr.expect <<-\\EOF &&\n+\terror: cannot find a hook named test-hook\n+\tEOF\n+\ttest_expect_code 1 git hook run test-hook 2>stderr.actual &&\n+\ttest_cmp stderr.expect stderr.actual\n+'\n+\n+test_expect_success 'git hook run: basic' '\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\techo Test hook\n+\tEOF\n+\n+\tcat >expect <<-\\EOF &&\n+\tTest hook\n+\tEOF\n+\tgit hook run test-hook 2>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git hook run: stdout and stderr both write to our stderr' '\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\techo >&1 Will end up on stderr\n+\techo >&2 Will end up on stderr\n+\tEOF\n+\n+\tcat >stderr.expect <<-\\EOF &&\n+\tWill end up on stderr\n+\tWill end up on stderr\n+\tEOF\n+\tgit hook run test-hook >stdout.actual 2>stderr.actual &&\n+\ttest_cmp stderr.expect stderr.actual &&\n+\ttest_must_be_empty stdout.actual\n+'\n+\n+test_expect_success 'git hook run: exit codes are passed along' '\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\texit 1\n+\tEOF\n+\n+\ttest_expect_code 1 git hook run test-hook &&\n+\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\texit 2\n+\tEOF\n+\n+\ttest_expect_code 2 git hook run test-hook &&\n+\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\texit 128\n+\tEOF\n+\n+\ttest_expect_code 128 git hook run test-hook &&\n+\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\texit 129\n+\tEOF\n+\n+\ttest_expect_code 129 git hook run test-hook\n+'\n+\n+test_expect_success 'git hook run arg u ments without -- is not allowed' '\n+\ttest_expect_code 129 git hook run test-hook arg u ments\n+'\n+\n+test_expect_success 'git hook run -- pass arguments' '\n+\twrite_script .git/hooks/test-hook <<-\\EOF &&\n+\techo $1\n+\techo $2\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\targ\n+\tu ments\n+\tEOF\n+\n+\tgit hook run test-hook -- arg \"u ments\" 2>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git hook run -- out-of-repo runs excluded' '\n+\twrite_script .git/hooks/test-hook <<-EOF &&\n+\techo Test hook\n+\tEOF\n+\n+\tnongit test_must_fail git hook run test-hook\n+'\n+\n+test_expect_success 'git -c core.hooksPath=<PATH> hook run' '\n+\tmkdir my-hooks &&\n+\twrite_script my-hooks/test-hook <<-\\EOF &&\n+\techo Hook ran $1 >>actual\n+\tEOF\n+\n+\tcat >expect <<-\\EOF &&\n+\tTest hook\n+\tHook ran one\n+\tHook ran two\n+\tHook ran three\n+\tHook ran four\n+\tEOF\n+\n+\t# Test various ways of specifying the path. See also\n+\t# t1350-config-hooks-path.sh\n+\t>actual &&\n+\tgit hook run test-hook -- ignored 2>>actual &&\n+\tgit -c core.hooksPath=my-hooks hook run test-hook -- one 2>>actual &&\n+\tgit -c core.hooksPath=my-hooks/ hook run test-hook -- two 2>>actual &&\n+\tgit -c core.hooksPath=\"$PWD/my-hooks\" hook run test-hook -- three 2>>actual &&\n+\tgit -c core.hooksPath=\"$PWD/my-hooks/\" hook run test-hook -- four 2>>actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_done\n-- \n2.34.1.1146.gb52885e7c44\n\n"},{"id":"444707","messageId":"patch-v6-03.17-a4cca074bcb-20211222T035755Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v6-00.17-00000000000-20211222T035755Z-avarab@gmail.com","subject":"[PATCH v6 03/17] gc: use hook library for pre-auto-gc hook","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-22T03:59:29Z","receivedAt":"2021-12-22T04:00:00Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the pre-auto-gc hook away from run-command.h to and over to the\nnew hook.h library. This uses the new run_hooks() wrapper.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/gc.c | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex bcef6a4c8da..4bbc58aae5b 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@@ -394,7 +395,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\"))\n \t\treturn 0;\n \treturn 1;\n }\n-- \n2.34.1.1146.gb52885e7c44\n\n"},{"id":"444708","messageId":"patch-v6-04.17-ce57ce1adcb-20211222T035755Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v6-00.17-00000000000-20211222T035755Z-avarab@gmail.com","subject":"[PATCH v6 04/17] am: convert {pre,post}-applypatch to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-22T03:59:30Z","receivedAt":"2021-12-22T04:00:00Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nTeach pre-applypatch and post-applypatch to use the hook.h library\ninstead of the run-command.h library.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/am.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 8677ea2348a..4b334cb7b12 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1609,7 +1609,7 @@ static void do_commit(const struct am_state *state)\n \tconst char *reflog_msg, *author, *committer = NULL;\n \tstruct strbuf sb = STRBUF_INIT;\n \n-\tif (run_hook_le(NULL, \"pre-applypatch\", NULL))\n+\tif (run_hooks(\"pre-applypatch\"))\n \t\texit(1);\n \n \tif (write_cache_as_tree(&tree, 0, NULL))\n@@ -1661,7 +1661,7 @@ 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\");\n \n \tstrbuf_release(&sb);\n }\n-- \n2.34.1.1146.gb52885e7c44\n\n"},{"id":"444709","messageId":"patch-v6-06.17-4c1a8951fc5-20211222T035755Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v6-00.17-00000000000-20211222T035755Z-avarab@gmail.com","subject":"[PATCH v6 06/17] rebase: convert pre-rebase to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-22T03:59:32Z","receivedAt":"2021-12-22T04:00:00Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the pre-rebase hook away from run-command.h to and over to the\nnew hook.h library.\n\nSince this hook needs arguments introduce a run_hooksl() wrapper, like\nrun_hooks(), but it takes varargs.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/rebase.c | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 34b4744e5f3..ac4120013a0 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@@ -1712,7 +1713,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t/* If a hook exists, give it a chance to interrupt*/\n \tif (!ok_to_skip_pre_rebase &&\n-\t    run_hook_le(NULL, \"pre-rebase\", options.upstream_arg,\n+\t    run_hooks_l(\"pre-rebase\", options.upstream_arg,\n \t\t\targc ? argv[0] : NULL, NULL))\n \t\tdie(_(\"The pre-rebase hook refused to rebase.\"));\n \n-- \n2.34.1.1146.gb52885e7c44\n\n"},{"id":"444710","messageId":"patch-v6-05.17-d6162fbef80-20211222T035755Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v6-00.17-00000000000-20211222T035755Z-avarab@gmail.com","subject":"[PATCH v6 05/17] hook API: add a run_hooks_l() wrapper","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-22T03:59:31Z","receivedAt":"2021-12-22T04:00:00Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add a run_hooks_l() wrapper, we'll use it in subsequent commits for\nthe simple cases of wanting to run a single hook under a given name\nalong with a list of arguments.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n hook.c | 14 ++++++++++++++\n hook.h | 10 ++++++++++\n 2 files changed, 24 insertions(+)\n\ndiff --git a/hook.c b/hook.c\nindex d67a114e62d..1ad123422b2 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -149,3 +149,17 @@ int run_hooks(const char *hook_name)\n \n \treturn run_hooks_opt(hook_name, &opt);\n }\n+\n+int run_hooks_l(const char *hook_name, ...)\n+{\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n+\tva_list ap;\n+\tconst char *arg;\n+\n+\tva_start(ap, hook_name);\n+\twhile ((arg = va_arg(ap, const char *)))\n+\t\tstrvec_push(&opt.args, arg);\n+\tva_end(ap);\n+\n+\treturn run_hooks_opt(hook_name, &opt);\n+}\ndiff --git a/hook.h b/hook.h\nindex 9c358789958..54528395953 100644\n--- a/hook.h\n+++ b/hook.h\n@@ -54,4 +54,14 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);\n  * run_hooks_opt\" initialized with \"RUN_HOOKS_OPT_INIT\".\n  */\n int run_hooks(const char *hook_name);\n+\n+/**\n+ * Like run_hooks(), a wrapper for run_hooks_opt().\n+ *\n+ * In addition to the wrapping behavior provided by run_hooks(), this\n+ * wrapper takes a list of strings terminated by a NULL\n+ * argument. These things will be used as positional arguments to the\n+ * hook. This function behaves like the old run_hook_le() API.\n+ */\n+int run_hooks_l(const char *hook_name, ...);\n #endif\n-- \n2.34.1.1146.gb52885e7c44\n\n"},{"id":"444711","messageId":"patch-v6-02.17-cfba5c139e7-20211222T035755Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v6-00.17-00000000000-20211222T035755Z-avarab@gmail.com","subject":"[PATCH v6 02/17] hook API: add a run_hooks() wrapper","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-22T03:59:28Z","receivedAt":"2021-12-22T04:00:00Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add a run_hooks() wrapper, we'll use it in subsequent commits for the\nsimple cases of wanting to run a single hook under a given name,\nwithout providing options such as \"env\" or \"args\".\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n hook.c | 7 +++++++\n hook.h | 6 ++++++\n 2 files changed, 13 insertions(+)\n\ndiff --git a/hook.c b/hook.c\nindex a0917cf877c..d67a114e62d 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -142,3 +142,10 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)\n \trun_hooks_opt_clear(options);\n \treturn ret;\n }\n+\n+int run_hooks(const char *hook_name)\n+{\n+\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n+\n+\treturn run_hooks_opt(hook_name, &opt);\n+}\ndiff --git a/hook.h b/hook.h\nindex 782385cc235..9c358789958 100644\n--- a/hook.h\n+++ b/hook.h\n@@ -48,4 +48,10 @@ int hook_exists(const char *hookname);\n  * error().\n  */\n int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);\n+\n+/**\n+ * A wrapper for run_hooks_opt() which provides a dummy \"struct\n+ * run_hooks_opt\" initialized with \"RUN_HOOKS_OPT_INIT\".\n+ */\n+int run_hooks(const char *hook_name);\n #endif\n-- \n2.34.1.1146.gb52885e7c44\n\n"},{"id":"444712","messageId":"patch-v6-07.17-d8aa5e8345f-20211222T035755Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v6-00.17-00000000000-20211222T035755Z-avarab@gmail.com","subject":"[PATCH v6 07/17] am: convert applypatch-msg to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-22T03:59:33Z","receivedAt":"2021-12-22T04:00:04Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nTeach applypatch-msg to use the hook.h library instead of the\nrun-command.h library.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/am.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 4b334cb7b12..ae0c484dcba 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -448,7 +448,7 @@ static int run_applypatch_msg_hook(struct am_state *state)\n \tint ret;\n \n \tassert(state->msg);\n-\tret = run_hook_le(NULL, \"applypatch-msg\", am_path(state, \"final-commit\"), NULL);\n+\tret = run_hooks_l(\"applypatch-msg\", am_path(state, \"final-commit\"), NULL);\n \n \tif (!ret) {\n \t\tFREE_AND_NULL(state->msg);\n-- \n2.34.1.1146.gb52885e7c44\n\n"},{"id":"444713","messageId":"patch-v6-10.17-bff7c1513ca-20211222T035755Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v6-00.17-00000000000-20211222T035755Z-avarab@gmail.com","subject":"[PATCH v6 10/17] hooks: convert worktree 'post-checkout' hook to hook library","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-22T03:59:36Z","receivedAt":"2021-12-22T04:00:04Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the running of the 'post-checkout' hook away from run-command.h\nto the new hook.h library in builtin/worktree.c. For this special case\nwe need a change to the hook API to teach it to run the hook from a\ngiven directory.\n\nWe cannot skip the \"absolute_path\" flag and just check if \"dir\" is\nspecified as we'd then fail to find our hook in the new dir we'd\nchdir() to. We currently don't have a use-case for running a hook not\nin our \"base\" repository at a given absolute path, so let's have \"dir\"\nimply absolute_path(find_hook(hook_name)).\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/worktree.c | 26 +++++++++++---------------\n hook.c             |  8 ++++++++\n hook.h             |  6 ++++++\n 3 files changed, 25 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex a396cfdc64e..6f03afad974 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -382,21 +382,17 @@ 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\tstruct child_process cp = CHILD_PROCESS_INIT;\n-\t\t\tcp.no_stdin = 1;\n-\t\t\tcp.stdout_to_stderr = 1;\n-\t\t\tcp.dir = path;\n-\t\t\tstrvec_pushv(&cp.env_array, env);\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;\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_opt(\"post-checkout\", &opt);\n \t}\n \n \tstrvec_clear(&child_env);\ndiff --git a/hook.c b/hook.c\nindex 1ad123422b2..69a215b2c3c 100644\n--- a/hook.c\n+++ b/hook.c\n@@ -57,6 +57,7 @@ static int pick_next_hook(struct child_process *cp,\n \tstrvec_pushv(&cp->env_array, hook_cb->options->env.v);\n \tcp->stdout_to_stderr = 1;\n \tcp->trace2_hook_name = hook_cb->hook_name;\n+\tcp->dir = hook_cb->options->dir;\n \n \tstrvec_push(&cp->args, hook_path);\n \tstrvec_pushv(&cp->args, hook_cb->options->args.v);\n@@ -109,6 +110,7 @@ static void run_hooks_opt_clear(struct run_hooks_opt *options)\n \n int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)\n {\n+\tstruct strbuf abs_path = STRBUF_INIT;\n \tstruct hook_cb_data cb_data = {\n \t\t.rc = 0,\n \t\t.hook_name = hook_name,\n@@ -130,6 +132,11 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)\n \t}\n \n \tcb_data.hook_path = hook_path;\n+\tif (options->dir) {\n+\t\tstrbuf_add_absolute_path(&abs_path, hook_path);\n+\t\tcb_data.hook_path = abs_path.buf;\n+\t}\n+\n \trun_processes_parallel_tr2(jobs,\n \t\t\t\t   pick_next_hook,\n \t\t\t\t   notify_start_failure,\n@@ -139,6 +146,7 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)\n \t\t\t\t   hook_name);\n \tret = cb_data.rc;\n cleanup:\n+\tstrbuf_release(&abs_path);\n \trun_hooks_opt_clear(options);\n \treturn ret;\n }\ndiff --git a/hook.h b/hook.h\nindex 54528395953..18d90aedf14 100644\n--- a/hook.h\n+++ b/hook.h\n@@ -12,6 +12,12 @@ struct run_hooks_opt\n \n \t/* Emit an error if the hook is missing */\n \tunsigned int error_if_missing:1;\n+\n+\t/**\n+\t * An optional initial working directory for the hook,\n+\t * translates to \"struct child_process\"'s \"dir\" member.\n+\t */\n+\tconst char *dir;\n };\n \n #define RUN_HOOKS_OPT_INIT { \\\n-- \n2.34.1.1146.gb52885e7c44\n\n"},{"id":"444715","messageId":"patch-v6-09.17-d3107034806-20211222T035755Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v6-00.17-00000000000-20211222T035755Z-avarab@gmail.com","subject":"[PATCH v6 09/17] hooks: convert non-worktree 'post-checkout' hook to hook library","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-22T03:59:35Z","receivedAt":"2021-12-22T04:00:04Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the running of the 'post-checkout' hook away from run-command.h\nto the new hook.h library, except in the case of\nbuiltin/worktree.c. That special-case will be handled in a subsequent\ncommit.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/checkout.c | 3 ++-\n builtin/clone.c    | 3 ++-\n reset.c            | 3 ++-\n 3 files changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 72beeb49aa9..e2e95445407 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@@ -114,7 +115,7 @@ static void branch_info_release(struct branch_info *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+\treturn run_hooks_l(\"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);\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex fb377b27657..ee27b9f8114 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@@ -705,7 +706,7 @@ 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+\terr |= run_hooks_l(\"post-checkout\", oid_to_hex(null_oid()),\n \t\t\t   oid_to_hex(&oid), \"1\", NULL);\n \n \tif (!err && (option_recurse_submodules.nr > 0)) {\ndiff --git a/reset.c b/reset.c\nindex f214df3d96c..0881e636915 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@@ -127,7 +128,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,\n \t\t\t\t\t    reflog_head);\n \t}\n \tif (run_hook)\n-\t\trun_hook_le(NULL, \"post-checkout\",\n+\t\trun_hooks_l(\"post-checkout\",\n \t\t\t    oid_to_hex(orig ? orig : null_oid()),\n \t\t\t    oid_to_hex(oid), \"1\", NULL);\n \n-- \n2.34.1.1146.gb52885e7c44\n\n"},{"id":"444714","messageId":"patch-v6-08.17-6f8d3754b4f-20211222T035755Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v6-00.17-00000000000-20211222T035755Z-avarab@gmail.com","subject":"[PATCH v6 08/17] merge: convert post-merge to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-22T03:59:34Z","receivedAt":"2021-12-22T04:00:05Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nTeach post-merge to use the hook.h library instead of the\nrun-command.h library to run hooks.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/merge.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 5f0476b0b76..5be3009c2a2 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -487,7 +487,7 @@ 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+\trun_hooks_l(\"post-merge\", squash ? \"1\" : \"0\", NULL);\n \n \tapply_autostash(git_path_merge_autostash(the_repository));\n \tstrbuf_release(&reflog_message);\n-- \n2.34.1.1146.gb52885e7c44\n\n"},{"id":"444716","messageId":"patch-v6-11.17-7d9c0a73568-20211222T035755Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v6-00.17-00000000000-20211222T035755Z-avarab@gmail.com","subject":"[PATCH v6 11/17] git hook run: add an --ignore-missing flag","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-22T03:59:37Z","receivedAt":"2021-12-22T04:00:05Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"For certain one-shot hooks we'd like to optimistically run them, and\nnot complain if they don't exist.\n\nThis was already supported by the underlying hook.c library, but had\nnot been exposed via \"git hook run\". The command version of this will\nbe used by send-email in a subsequent commit.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Documentation/git-hook.txt | 10 +++++++++-\n builtin/hook.c             |  8 ++++++--\n t/t1800-hook.sh            |  5 +++++\n 3 files changed, 20 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt\nindex e39b1b5d069..77c3a8ad909 100644\n--- a/Documentation/git-hook.txt\n+++ b/Documentation/git-hook.txt\n@@ -8,7 +8,7 @@ git-hook - Run git hooks\n SYNOPSIS\n --------\n [verse]\n-'git hook' run <hook-name> [-- <hook-args>]\n+'git hook' run [--ignore-missing] <hook-name> [-- <hook-args>]\n \n DESCRIPTION\n -----------\n@@ -28,6 +28,14 @@ Any positional arguments to the hook should be passed after a\n mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See\n linkgit:githooks[5] for arguments hooks might expect (if any).\n \n+OPTIONS\n+-------\n+\n+--ignore-missing::\n+\tIgnore any missing hook by quietly returning zero. Used for\n+\ttools that want to do a blind one-shot run of a hook that may\n+\tor may not be present.\n+\n SEE ALSO\n --------\n linkgit:githooks[5]\ndiff --git a/builtin/hook.c b/builtin/hook.c\nindex 9b67ff50cef..54e5c6ec933 100644\n--- a/builtin/hook.c\n+++ b/builtin/hook.c\n@@ -7,7 +7,7 @@\n #include \"strvec.h\"\n \n #define BUILTIN_HOOK_RUN_USAGE \\\n-\tN_(\"git hook run <hook-name> [-- <hook-args>]\")\n+\tN_(\"git hook run [--ignore-missing] <hook-name> [-- <hook-args>]\")\n \n static const char * const builtin_hook_usage[] = {\n \tBUILTIN_HOOK_RUN_USAGE,\n@@ -23,8 +23,11 @@ static int run(int argc, const char **argv, const char *prefix)\n {\n \tint i;\n \tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n+\tint ignore_missing = 0;\n \tconst char *hook_name;\n \tstruct option run_options[] = {\n+\t\tOPT_BOOL(0, \"ignore-missing\", &ignore_missing,\n+\t\t\t N_(\"silently ignore missing requested <hook-name>\")),\n \t\tOPT_END(),\n \t};\n \tint ret;\n@@ -52,7 +55,8 @@ static int run(int argc, const char **argv, const char *prefix)\n \tgit_config(git_default_config, NULL);\n \n \thook_name = argv[0];\n-\topt.error_if_missing = 1;\n+\tif (!ignore_missing)\n+\t\topt.error_if_missing = 1;\n \tret = run_hooks_opt(hook_name, &opt);\n \tif (ret < 0) /* error() return */\n \t\tret = 1;\ndiff --git a/t/t1800-hook.sh b/t/t1800-hook.sh\nindex 3aea1b105f0..29718aa9913 100755\n--- a/t/t1800-hook.sh\n+++ b/t/t1800-hook.sh\n@@ -21,6 +21,11 @@ test_expect_success 'git hook run: nonexistent hook' '\n \ttest_cmp stderr.expect stderr.actual\n '\n \n+test_expect_success 'git hook run: nonexistent hook with --ignore-missing' '\n+\tgit hook run --ignore-missing does-not-exist 2>stderr.actual &&\n+\ttest_must_be_empty stderr.actual\n+'\n+\n test_expect_success 'git hook run: basic' '\n \twrite_script .git/hooks/test-hook <<-EOF &&\n \techo Test hook\n-- \n2.34.1.1146.gb52885e7c44\n\n"},{"id":"444717","messageId":"patch-v6-12.17-8ea3b250dff-20211222T035755Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v6-00.17-00000000000-20211222T035755Z-avarab@gmail.com","subject":"[PATCH v6 12/17] send-email: use 'git hook run' for 'sendemail-validate'","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-22T03:59:38Z","receivedAt":"2021-12-22T04:00:07Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nChange the \"sendmail-validate\" hook to be run via the \"git hook run\"\nwrapper instead of via a direct invocation.\n\nThis is the smallest possibly change to get \"send-email\" using \"git\nhook run\". We still check the hook itself with \"-x\", and set a\n\"GIT_DIR\" variable, both of which are asserted by our tests. We'll\nneed to get rid of this special behavior if we start running N hooks,\nbut for now let's be as close to bug-for-bug compatible as possible.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-send-email.perl   | 22 ++++++++++++++--------\n t/t9001-send-email.sh |  4 ++--\n 2 files changed, 16 insertions(+), 10 deletions(-)\n\ndiff --git a/git-send-email.perl b/git-send-email.perl\nindex 04087221aa7..a98460bdb96 100755\n--- a/git-send-email.perl\n+++ b/git-send-email.perl\n@@ -225,13 +225,13 @@ sub format_2822_time {\n my $editor;\n \n sub system_or_msg {\n-\tmy ($args, $msg) = @_;\n+\tmy ($args, $msg, $cmd_name) = @_;\n \tsystem(@$args);\n \tmy $signalled = $? & 127;\n \tmy $exit_code = $? >> 8;\n \treturn unless $signalled or $exit_code;\n \n-\tmy @sprintf_args = ($args->[0], $exit_code);\n+\tmy @sprintf_args = ($cmd_name ? $cmd_name : $args->[0], $exit_code);\n \tif (defined $msg) {\n \t\t# Quiet the 'redundant' warning category, except we\n \t\t# need to support down to Perl 5.8, so we can't do a\n@@ -2075,10 +2075,10 @@ sub validate_patch {\n \tmy ($fn, $xfer_encoding) = @_;\n \n \tif ($repo) {\n+\t\tmy $hook_name = 'sendemail-validate';\n \t\tmy $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks');\n \t\trequire File::Spec;\n-\t\tmy $validate_hook = File::Spec->catfile($hooks_path,\n-\t\t\t\t\t    'sendemail-validate');\n+\t\tmy $validate_hook = File::Spec->catfile($hooks_path, $hook_name);\n \t\tmy $hook_error;\n \t\tif (-x $validate_hook) {\n \t\t\trequire Cwd;\n@@ -2088,13 +2088,19 @@ sub validate_patch {\n \t\t\tchdir($repo->wc_path() or $repo->repo_path())\n \t\t\t\tor die(\"chdir: $!\");\n \t\t\tlocal $ENV{\"GIT_DIR\"} = $repo->repo_path();\n-\t\t\t$hook_error = system_or_msg([$validate_hook, $target]);\n+\t\t\tmy @cmd = (\"git\", \"hook\", \"run\", \"--ignore-missing\",\n+\t\t\t\t    $hook_name, \"--\");\n+\t\t\tmy @cmd_msg = (@cmd, \"<patch>\");\n+\t\t\tmy @cmd_run = (@cmd, $target);\n+\t\t\t$hook_error = system_or_msg(\\@cmd_run, undef, \"@cmd_msg\");\n \t\t\tchdir($cwd_save) or die(\"chdir: $!\");\n \t\t}\n \t\tif ($hook_error) {\n-\t\t\tdie sprintf(__(\"fatal: %s: rejected by sendemail-validate hook\\n\" .\n-\t\t\t\t       \"%s\\n\" .\n-\t\t\t\t       \"warning: no patches were sent\\n\"), $fn, $hook_error);\n+\t\t\t$hook_error = sprintf(__(\"fatal: %s: rejected by %s hook\\n\" .\n+\t\t\t\t\t\t $hook_error . \"\\n\" .\n+\t\t\t\t\t\t \"warning: no patches were sent\\n\"),\n+\t\t\t\t\t      $fn, $hook_name);\n+\t\t\tdie $hook_error;\n \t\t}\n \t}\n \ndiff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\nindex aa0c20499ba..84d0f40d76a 100755\n--- a/t/t9001-send-email.sh\n+++ b/t/t9001-send-email.sh\n@@ -539,7 +539,7 @@ test_expect_success $PREREQ \"--validate respects relative core.hooksPath path\" '\n \ttest_path_is_file my-hooks.ran &&\n \tcat >expect <<-EOF &&\n \tfatal: longline.patch: rejected by sendemail-validate hook\n-\tfatal: command '\"'\"'my-hooks/sendemail-validate'\"'\"' died with exit code 1\n+\tfatal: command '\"'\"'git hook run --ignore-missing sendemail-validate -- <patch>'\"'\"' died with exit code 1\n \twarning: no patches were sent\n \tEOF\n \ttest_cmp expect actual\n@@ -558,7 +558,7 @@ test_expect_success $PREREQ \"--validate respects absolute core.hooksPath path\" '\n \ttest_path_is_file my-hooks.ran &&\n \tcat >expect <<-EOF &&\n \tfatal: longline.patch: rejected by sendemail-validate hook\n-\tfatal: command '\"'\"'$hooks_path/sendemail-validate'\"'\"' died with exit code 1\n+\tfatal: command '\"'\"'git hook run --ignore-missing sendemail-validate -- <patch>'\"'\"' died with exit code 1\n \twarning: no patches were sent\n \tEOF\n \ttest_cmp expect actual\n-- \n2.34.1.1146.gb52885e7c44\n\n"},{"id":"444718","messageId":"patch-v6-13.17-a184afd1ffd-20211222T035755Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v6-00.17-00000000000-20211222T035755Z-avarab@gmail.com","subject":"[PATCH v6 13/17] git-p4: use 'git hook' to run hooks","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-22T03:59:39Z","receivedAt":"2021-12-22T04:00:07Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nInstead of duplicating the behavior of run-command.h:run_hook_le() in\nPython, we can directly call 'git hook run'. We emulate the existence\ncheck with the --ignore-missing flag.\n\nWe're dropping the \"verbose\" handling added in 9f59ca4d6af (git-p4:\ncreate new function run_git_hook, 2020-02-11), those who want\ndiagnostic output about how hooks are run are now able to get that via\ne.g. the trace2 facility and GIT_TRACE=1.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n git-p4.py | 70 +++++--------------------------------------------------\n 1 file changed, 6 insertions(+), 64 deletions(-)\n\ndiff --git a/git-p4.py b/git-p4.py\nindex 2b4500226aa..3b54168eb4a 100755\n--- a/git-p4.py\n+++ b/git-p4.py\n@@ -208,70 +208,12 @@ 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-        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-\n+    args = ['git', 'hook', 'run', '--ignore-missing', cmd]\n+    if param:\n+        args.append(\"--\")\n+        for p in param:\n+            args.append(p)\n+    return subprocess.call(args) == 0\n \n def write_pipe(c, stdin):\n     if verbose:\n-- \n2.34.1.1146.gb52885e7c44\n\n"},{"id":"444719","messageId":"patch-v6-14.17-1a43e50617f-20211222T035755Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v6-00.17-00000000000-20211222T035755Z-avarab@gmail.com","subject":"[PATCH v6 14/17] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-22T03:59:40Z","receivedAt":"2021-12-22T04:00:09Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove these hooks hook away from run-command.h to and over to the new\nhook.h library.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n commit.c | 15 ++++++++-------\n 1 file changed, 8 insertions(+), 7 deletions(-)\n\ndiff --git a/commit.c b/commit.c\nindex a348f085b2b..931f65d56d6 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@@ -1702,22 +1703,22 @@ size_t ignore_non_trailer(const char *buf, size_t len)\n int run_commit_hook(int editor_is_used, 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;\n \tva_list args;\n-\tint ret;\n+\tconst char *arg;\n \n-\tstrvec_pushf(&hook_env, \"GIT_INDEX_FILE=%s\", index_file);\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-\treturn ret;\n+\treturn run_hooks_opt(name, &opt);\n }\n-- \n2.34.1.1146.gb52885e7c44\n\n"},{"id":"444720","messageId":"patch-v6-15.17-08b7e63ba5b-20211222T035755Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v6-00.17-00000000000-20211222T035755Z-avarab@gmail.com","subject":"[PATCH v6 15/17] read-cache: convert post-index-change to use hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-22T03:59:41Z","receivedAt":"2021-12-22T04:00:13Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the post-index-change hook away from run-command.h to and over to\nthe new hook.h library.\n\nThis removes the last direct user of \"run_hook_ve()\" outside of\nrun-command.c (\"run_hook_le()\" still uses it). So we can make the\nfunction static now. A subsequent commit will remove this code\nentirely when \"run_hook_le()\" itself goes away.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n read-cache.c  | 3 ++-\n run-command.c | 2 +-\n run-command.h | 1 -\n 3 files changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex cbe73f14e5e..a7fbf144fec 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -28,6 +28,7 @@\n #include \"sparse-index.h\"\n #include \"csum-file.h\"\n #include \"promisor-remote.h\"\n+#include \"hook.h\"\n \n /* Mask for the name length in ce_flags in the on-disk index */\n \n@@ -3150,7 +3151,7 @@ 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+\trun_hooks_l(\"post-index-change\",\n \t\t\tistate->updated_workdir ? \"1\" : \"0\",\n \t\t\tistate->updated_skipworktree ? \"1\" : \"0\", NULL);\n \tistate->updated_workdir = 0;\ndiff --git a/run-command.c b/run-command.c\nindex 3ea2c2fc101..ea09de040a7 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -1313,7 +1313,7 @@ int async_with_fork(void)\n #endif\n }\n \n-int run_hook_ve(const char *const *env, const char *name, va_list args)\n+static 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;\ndiff --git a/run-command.h b/run-command.h\nindex 2be5f5d6422..88a398c300a 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -235,7 +235,6 @@ int run_command(struct child_process *);\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-- \n2.34.1.1146.gb52885e7c44\n\n"},{"id":"444721","messageId":"patch-v6-16.17-c47b36ab41a-20211222T035755Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v6-00.17-00000000000-20211222T035755Z-avarab@gmail.com","subject":"[PATCH v6 16/17] receive-pack: convert push-to-checkout hook to hook.h","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-22T03:59:42Z","receivedAt":"2021-12-22T04:00:14Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nMove the push-to-checkout hook away from run-command.h to and over to\nthe new hook.h library.\n\nThis removes the last direct user of run_hook_le(), so we could remove\nthat function now, but let's leave that to a follow-up cleanup commit.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/receive-pack.c | 7 +++++--\n 1 file changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 4f92e6f059d..e99b1ecd103 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1424,9 +1424,12 @@ 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;\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_opt(push_to_checkout_hook, &opt))\n \t\treturn \"push-to-checkout hook declined\";\n \telse\n \t\treturn NULL;\n-- \n2.34.1.1146.gb52885e7c44\n\n"},{"id":"444722","messageId":"patch-v6-17.17-7b99a4b633c-20211222T035755Z-avarab@gmail.com","threadId":"56717","inReplyTo":"cover-v6-00.17-00000000000-20211222T035755Z-avarab@gmail.com","subject":"[PATCH v6 17/17] run-command: remove old run_hook_{le,ve}() hook API","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-22T03:59:43Z","receivedAt":"2021-12-22T04:00:15Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Emily Shaffer <emilyshaffer@google.com>\n\nThe new hook.h library has replaced all run-command.h hook-related\nfunctionality. So let's delete this dead code.\n\nSigned-off-by: Emily Shaffer <emilyshaffer@google.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n run-command.c | 33 ---------------------------------\n run-command.h | 16 ----------------\n 2 files changed, 49 deletions(-)\n\ndiff --git a/run-command.c b/run-command.c\nindex ea09de040a7..f32e4fe1326 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -1313,39 +1313,6 @@ int async_with_fork(void)\n #endif\n }\n \n-static 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-\tif (env)\n-\t\tstrvec_pushv(&hook.env_array, (const char **)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 88a398c300a..07bed6c31b4 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -220,22 +220,6 @@ int finish_command_in_signal(struct child_process *);\n  */\n int run_command(struct child_process *);\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-\n /*\n  * Trigger an auto-gc\n  */\n-- \n2.34.1.1146.gb52885e7c44\n\n"},{"id":"445638","messageId":"kl6lr19k7lcb.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56717","inReplyTo":"cover-v6-00.17-00000000000-20211222T035755Z-avarab@gmail.com","subject":"Re: [PATCH v6 00/17] hook.[ch]: new library to run hooks + simple hook conversion","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-06T18:47:16Z","receivedAt":"2022-01-06T18:47:20Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Ævar Arnfjörð Bjarmason         <avarab@gmail.com> writes:\n\n> A trivial update to v5 to rebase it past conflicts with topics that\n> recently landed on \"master\". For v5 see:\n> https://lore.kernel.org/git/cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com/\n>\n> Emily Shaffer (14):\n>   hook: add 'run' subcommand\n>   gc: use hook library for pre-auto-gc hook\n>   am: convert {pre,post}-applypatch to use hook.h\n>   rebase: convert pre-rebase to use hook.h\n>   am: convert applypatch-msg to use hook.h\n>   merge: convert post-merge to use hook.h\n>   hooks: convert non-worktree 'post-checkout' hook to hook library\n>   hooks: convert worktree 'post-checkout' hook to hook library\n>   send-email: use 'git hook run' for 'sendemail-validate'\n>   git-p4: use 'git hook' to run hooks\n>   commit: convert {pre-commit,prepare-commit-msg} hook to hook.h\n>   read-cache: convert post-index-change to use hook.h\n>   receive-pack: convert push-to-checkout hook to hook.h\n>   run-command: remove old run_hook_{le,ve}() hook API\n>\n> Ævar Arnfjörð Bjarmason (3):\n>   hook API: add a run_hooks() wrapper\n>   hook API: add a run_hooks_l() wrapper\n>   git hook run: add an --ignore-missing flag\n>\n>  .gitignore                 |   1 +\n>  Documentation/git-hook.txt |  45 +++++++++++++\n>  Documentation/githooks.txt |   4 ++\n>  Makefile                   |   1 +\n>  builtin.h                  |   1 +\n>  builtin/am.c               |   6 +-\n>  builtin/checkout.c         |   3 +-\n>  builtin/clone.c            |   3 +-\n>  builtin/gc.c               |   3 +-\n>  builtin/hook.c             |  84 +++++++++++++++++++++++\n>  builtin/merge.c            |   2 +-\n>  builtin/rebase.c           |   3 +-\n>  builtin/receive-pack.c     |   7 +-\n>  builtin/worktree.c         |  26 +++----\n>  command-list.txt           |   1 +\n>  commit.c                   |  15 +++--\n>  git-p4.py                  |  70 ++-----------------\n>  git-send-email.perl        |  22 +++---\n>  git.c                      |   1 +\n>  hook.c                     | 131 ++++++++++++++++++++++++++++++++++++\n>  hook.h                     |  57 ++++++++++++++++\n>  read-cache.c               |   3 +-\n>  reset.c                    |   3 +-\n>  run-command.c              |  33 ---------\n>  run-command.h              |  17 -----\n>  t/t1800-hook.sh            | 134 +++++++++++++++++++++++++++++++++++++\n>  t/t9001-send-email.sh      |   4 +-\n>  27 files changed, 522 insertions(+), 158 deletions(-)\n>  create mode 100644 Documentation/git-hook.txt\n>  create mode 100644 builtin/hook.c\n>  create mode 100755 t/t1800-hook.sh\n>\n> Range-diff against v5:\n>  1:  4ca52feebb8 =  1:  ba6fd47482e hook: add 'run' subcommand\n>  2:  6275b97a306 =  2:  cfba5c139e7 hook API: add a run_hooks() wrapper\n>  3:  b5b3051b2e5 =  3:  a4cca074bcb gc: use hook library for pre-auto-gc hook\n>  4:  c88eb5d4c25 =  4:  ce57ce1adcb am: convert {pre,post}-applypatch to use hook.h\n>  5:  1d8f7b7e4c1 =  5:  d6162fbef80 hook API: add a run_hooks_l() wrapper\n>  6:  d49a1444345 =  6:  4c1a8951fc5 rebase: convert pre-rebase to use hook.h\n>  7:  191fdad0165 =  7:  d8aa5e8345f am: convert applypatch-msg to use hook.h\n>  8:  119b92fbeae =  8:  6f8d3754b4f merge: convert post-merge to use hook.h\n>  9:  359ba416e84 !  9:  d3107034806 hooks: convert non-worktree 'post-checkout' hook to hook library\n>     @@ builtin/checkout.c\n>       #include \"ll-merge.h\"\n>       #include \"lockfile.h\"\n>       #include \"merge-recursive.h\"\n>     -@@ builtin/checkout.c: struct branch_info {\n>     +@@ builtin/checkout.c: static void branch_info_release(struct branch_info *info)\n>       static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,\n>       \t\t\t      int changed)\n>       {\n> 10:  b7599be95a7 ! 10:  bff7c1513ca hooks: convert worktree 'post-checkout' hook to hook library\n>     @@ builtin/worktree.c: static int add_worktree(const char *path, const char *refnam\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\tstruct child_process cp = CHILD_PROCESS_INIT;\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\tstrvec_pushv(&cp.env_array, env);\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> 11:  f1c84d7f627 = 11:  7d9c0a73568 git hook run: add an --ignore-missing flag\n> 12:  4e0f94d9102 = 12:  8ea3b250dff send-email: use 'git hook run' for 'sendemail-validate'\n> 13:  e858f332a62 = 13:  a184afd1ffd git-p4: use 'git hook' to run hooks\n> 14:  9a5956cc028 = 14:  1a43e50617f commit: convert {pre-commit,prepare-commit-msg} hook to hook.h\n> 15:  6fd47c4c499 = 15:  08b7e63ba5b read-cache: convert post-index-change to use hook.h\n> 16:  b201ea46f4b = 16:  c47b36ab41a receive-pack: convert push-to-checkout hook to hook.h\n> 17:  281d17b04db ! 17:  7b99a4b633c run-command: remove old run_hook_{le,ve}() hook API\n>     @@ run-command.c: int async_with_fork(void)\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>     +-\tif (env)\n>     +-\t\tstrvec_pushv(&hook.env_array, (const char **)env);\n>      -\thook.no_stdin = 1;\n>      -\thook.stdout_to_stderr = 1;\n>      -\thook.trace2_hook_name = name;\n> -- \n> 2.34.1.1146.gb52885e7c44\n\n\nThe Google folks picked up this series for our \"review club\" sessions\n(which is open to the public, as you know ;)). As Junio mentioned in the\nlast \"What's Cooking\" [1], he would like serious review on the patches\nyou contributed, so that is where we focused most of our attention.\n\nThe code itself looks quite good, but one concern that came up was that\nthe motivation behind this series doesn't seem immediately obvious to\nfirst time readers. This series has existed in some form for a very long\ntime; the first patches for config-based hooks I could find date back to\n2019! [2]. So keeping everyone on the same page is definitely hard, but\nit's worthwhile if it means that someone can pick up the series without\nhaving to be familiar with the prior work.\n\nWith that in mind, your original cover letter [3] does a good job at\ndescribing the \"what\" of the series - what it does (extend the hook API\nand replace calls), what it is (an incremental restart of an ejected\ntopic). However, it's hard to recreate the motivation behind this series\nbased on the cover letter and its papertrail. Much of the Google team is\nfairly familiar with the feature, but newcomers (including myself) have\na hard time piecing together the story based on this thread alone.\n\nI found myself asking some questions when reading this cover letter, and\nhere are some suggestions to make this friendlier for new readers:\n\n* Why do we want config based hooks in the first place?\n\n  Convincing us that we want this is Emily's job, not yours, but it\n  helps to know how this fits in with the whole thing. As a convenience,\n  linking to Emily's original series would save the reader a lot of\n  thread-following.\n\n* How does this series contribute to config based hooks?\n* Why did we need this reroll in the first place?\n* How does this series meet the goals of the reroll? \n\n  These are pretty difficult to pin down without being familiar with the\n  original series too. Emily probably knows the anwers to these, so you\n  have at least one person who can review the series based on these\n  questions, but it's pretty challenging for others to figure this out.\n\n  This question is probably best answered in the cover letter, or at the\n  very least, links to suggested reading and the order in which to read\n  them.\n\n* What benefit does this series bring irrespective of config based\n  hooks?\n\n  This is best answered in the cover letter as well, but the payoff is\n  way bigger than the previous set of questions. If you can sell\n  reviewers on the idea that this series independently mergeable, is\n  good for the project on its own (I think that it is!), _and_ it\n  contributes to config based hooks, then reviewers have a good reason\n  to look at it even if they aren't particularly invested in config\n  based hooks. I think this is even more important because we want\n  reviews saying \"yes this series is good for the project\".\n\nTL;DR this is basically a long-winded way of saying that for a\nlong-running work in progress like this one, new reviewers are probably\nscared off by having to recreate the context leading up to this series,\nand anything we can do to lessen that burden will be greatly\nappreciated :)\n\n[1] https://lore.kernel.org/git/xmqq1r1lfwyq.fsf@gitster.g\n[2] https://lore.kernel.org/git/20191210023335.49987-1-emilyshaffer@google.com/\n[3] https://lore.kernel.org/git/cover-00.13-00000000000-20211012T131934Z-avarab@gmail.com/\n"},{"id":"445639","messageId":"kl6lmtk87l1t.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56717","inReplyTo":"patch-v6-02.17-cfba5c139e7-20211222T035755Z-avarab@gmail.com","subject":"Re: [PATCH v6 02/17] hook API: add a run_hooks() wrapper","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-06T18:53:34Z","receivedAt":"2022-01-06T18:53:41Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Ævar Arnfjörð Bjarmason         <avarab@gmail.com> writes:\n\n> Add a run_hooks() wrapper, we'll use it in subsequent commits for the\n> simple cases of wanting to run a single hook under a given name,\n> without providing options such as \"env\" or \"args\".\n>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> ---\n>  hook.c | 7 +++++++\n>  hook.h | 6 ++++++\n>  2 files changed, 13 insertions(+)\n>\n> diff --git a/hook.c b/hook.c\n> index a0917cf877c..d67a114e62d 100644\n> --- a/hook.c\n> +++ b/hook.c\n> @@ -142,3 +142,10 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)\n>  \trun_hooks_opt_clear(options);\n>  \treturn ret;\n>  }\n> +\n> +int run_hooks(const char *hook_name)\n> +{\n> +\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n> +\n> +\treturn run_hooks_opt(hook_name, &opt);\n> +}\n> diff --git a/hook.h b/hook.h\n> index 782385cc235..9c358789958 100644\n> --- a/hook.h\n> +++ b/hook.h\n> @@ -48,4 +48,10 @@ int hook_exists(const char *hookname);\n>   * error().\n>   */\n>  int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);\n> +\n> +/**\n> + * A wrapper for run_hooks_opt() which provides a dummy \"struct\n> + * run_hooks_opt\" initialized with \"RUN_HOOKS_OPT_INIT\".\n> + */\n> +int run_hooks(const char *hook_name);\n>  #endif\n> -- \n> 2.34.1.1146.gb52885e7c44\n\nThis patch looks good. As a matter of personal taste, it's not ideal\nthat this is dead code, but it gets tested immediately in the subsequent\npatches anyway, and those patches are pretty clear.\n"},{"id":"445640","messageId":"kl6lk0fc7kx1.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56717","inReplyTo":"patch-v6-05.17-d6162fbef80-20211222T035755Z-avarab@gmail.com","subject":"Re: [PATCH v6 05/17] hook API: add a run_hooks_l() wrapper","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-06T18:56:26Z","receivedAt":"2022-01-06T18:56:31Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Ævar Arnfjörð Bjarmason         <avarab@gmail.com> writes:\n\n> Add a run_hooks_l() wrapper, we'll use it in subsequent commits for\n> the simple cases of wanting to run a single hook under a given name\n> along with a list of arguments.\n>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> ---\n>  hook.c | 14 ++++++++++++++\n>  hook.h | 10 ++++++++++\n>  2 files changed, 24 insertions(+)\n>\n> diff --git a/hook.c b/hook.c\n> index d67a114e62d..1ad123422b2 100644\n> --- a/hook.c\n> +++ b/hook.c\n> @@ -149,3 +149,17 @@ int run_hooks(const char *hook_name)\n>  \n>  \treturn run_hooks_opt(hook_name, &opt);\n>  }\n> +\n> +int run_hooks_l(const char *hook_name, ...)\n> +{\n> +\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n> +\tva_list ap;\n> +\tconst char *arg;\n> +\n> +\tva_start(ap, hook_name);\n> +\twhile ((arg = va_arg(ap, const char *)))\n> +\t\tstrvec_push(&opt.args, arg);\n> +\tva_end(ap);\n> +\n> +\treturn run_hooks_opt(hook_name, &opt);\n> +}\n> diff --git a/hook.h b/hook.h\n> index 9c358789958..54528395953 100644\n> --- a/hook.h\n> +++ b/hook.h\n> @@ -54,4 +54,14 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);\n>   * run_hooks_opt\" initialized with \"RUN_HOOKS_OPT_INIT\".\n>   */\n>  int run_hooks(const char *hook_name);\n> +\n> +/**\n> + * Like run_hooks(), a wrapper for run_hooks_opt().\n> + *\n> + * In addition to the wrapping behavior provided by run_hooks(), this\n> + * wrapper takes a list of strings terminated by a NULL\n> + * argument. These things will be used as positional arguments to the\n> + * hook. This function behaves like the old run_hook_le() API.\n> + */\n> +int run_hooks_l(const char *hook_name, ...);\n>  #endif\n> -- \n> 2.34.1.1146.gb52885e7c44\n\nSame comments as patch 2 [1], i.e. this looks good; it's technically\ndead code, but that's mostly a matter of taste.\n\n[1] https://lore.kernel.org/git/kl6lmtk87l1t.fsf@chooglen-macbookpro.roam.corp.google.com\n"},{"id":"445644","messageId":"kl6lh7ag7j7i.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56717","inReplyTo":"patch-v6-11.17-7d9c0a73568-20211222T035755Z-avarab@gmail.com","subject":"Re: [PATCH v6 11/17] git hook run: add an --ignore-missing flag","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2022-01-06T19:33:21Z","receivedAt":"2022-01-06T19:33:25Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Ævar Arnfjörð Bjarmason         <avarab@gmail.com> writes:\n\n> For certain one-shot hooks we'd like to optimistically run them, and\n> not complain if they don't exist.\n>\n> This was already supported by the underlying hook.c library, but had\n> not been exposed via \"git hook run\". The command version of this will\n> be used by send-email in a subsequent commit.\n>\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> ---\n>  Documentation/git-hook.txt | 10 +++++++++-\n>  builtin/hook.c             |  8 ++++++--\n>  t/t1800-hook.sh            |  5 +++++\n>  3 files changed, 20 insertions(+), 3 deletions(-)\n>\n> diff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt\n> index e39b1b5d069..77c3a8ad909 100644\n> --- a/Documentation/git-hook.txt\n> +++ b/Documentation/git-hook.txt\n> @@ -8,7 +8,7 @@ git-hook - Run git hooks\n>  SYNOPSIS\n>  --------\n>  [verse]\n> -'git hook' run <hook-name> [-- <hook-args>]\n> +'git hook' run [--ignore-missing] <hook-name> [-- <hook-args>]\n>  \n>  DESCRIPTION\n>  -----------\n> @@ -28,6 +28,14 @@ Any positional arguments to the hook should be passed after a\n>  mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See\n>  linkgit:githooks[5] for arguments hooks might expect (if any).\n>  \n> +OPTIONS\n> +-------\n> +\n> +--ignore-missing::\n> +\tIgnore any missing hook by quietly returning zero. Used for\n> +\ttools that want to do a blind one-shot run of a hook that may\n> +\tor may not be present.\n\nThe most obvious question whenever a new option is introduced is \"Is\nthis a descriptive and consistent option name?\" The answer we came to in\nreview club is yes, \"--ignore-missing\" is good :)\n\nHowever, the description isn't 100% clear about what it means for a hook\nto be missing (possible confusion: is a missing hook a hook that isn't\ndocumented in Documentation/githooks.txt?). I suspect that your wording\nis intentionally open-ended because Git will eventually look for hooks\nin the config as well as `.git/hooks`. Nevertheless, it would help to\nget a precise definition in the docs so that readers know what to expect\nand we can advise() users consistently.\n\n> +\n>  SEE ALSO\n>  --------\n>  linkgit:githooks[5]\n> diff --git a/builtin/hook.c b/builtin/hook.c\n> index 9b67ff50cef..54e5c6ec933 100644\n> --- a/builtin/hook.c\n> +++ b/builtin/hook.c\n> @@ -7,7 +7,7 @@\n>  #include \"strvec.h\"\n>  \n>  #define BUILTIN_HOOK_RUN_USAGE \\\n> -\tN_(\"git hook run <hook-name> [-- <hook-args>]\")\n> +\tN_(\"git hook run [--ignore-missing] <hook-name> [-- <hook-args>]\")\n>  \n>  static const char * const builtin_hook_usage[] = {\n>  \tBUILTIN_HOOK_RUN_USAGE,\n> @@ -23,8 +23,11 @@ static int run(int argc, const char **argv, const char *prefix)\n>  {\n>  \tint i;\n>  \tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n> +\tint ignore_missing = 0;\n>  \tconst char *hook_name;\n>  \tstruct option run_options[] = {\n> +\t\tOPT_BOOL(0, \"ignore-missing\", &ignore_missing,\n> +\t\t\t N_(\"silently ignore missing requested <hook-name>\")),\n>  \t\tOPT_END(),\n>  \t};\n>  \tint ret;\n> @@ -52,7 +55,8 @@ static int run(int argc, const char **argv, const char *prefix)\n>  \tgit_config(git_default_config, NULL);\n>  \n>  \thook_name = argv[0];\n> -\topt.error_if_missing = 1;\n> +\tif (!ignore_missing)\n> +\t\topt.error_if_missing = 1;\n\nIt's not clear why --ignore-missing isn't just the default behavior (and\njudging from the lines above, it looks like it used to be?). After some\ndebate, most of the Google reviewers liked this new default, but it was\ndifficult to assess the merits of the decision because the thought\nprocess wasn't obvious.\n\nHere's the line of reasoning: if we only consider the hooks in-tree, it\ncertainly seems like none of the callers actually want to error out if\nthe hook is missing. This suggests that the default behavior of ignoring\nmissing hooks is actually what we want, so why change the default\nbehavior and add another option to worry about?\n\nI can propose at least one reason, which is that \"git hook\" is also\nmeant to be invoked by end users in the shell and not just by programs.\nI used to use npm quite a lot, and a pattern I am quite fond of is to\nhave an npm script called \"precommit\" which runs my linters and\nformatters, and I'd make `npm run precommit` part of my pre-commit hook.\nBut a nice benefit is that I can also run `npm run precommit` at any\ntime to run all of my tooling, regardless of whether or not I am making\na commit. It would be pretty neat to be able to do away with npm and\njust use `git hook run pre-commit'. In this scenario, I would certainly\nexpect `git hook run pre-commit` to fail if there is no `pre-commit`\nhook, so yes, this new default behavior can make sense.\n\nBut I can't tell if this is something you had intended all along, or if\nyou had something else in mind. If anything, the original cover letter\n[1] _almost_ seems to suggest otherwise.\n\n  The new \"git hook\" command is (for now) only for the benefit of\n  in-tree non-C programs that need to run hooks, and this series\n  converts git-send-email and git-p4 to use it.\n\nIt's worth explaining the rationale in the commit message.\n\n>  \tret = run_hooks_opt(hook_name, &opt);\n>  \tif (ret < 0) /* error() return */\n>  \t\tret = 1;\n\n\n> diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh\n> index 3aea1b105f0..29718aa9913 100755\n> --- a/t/t1800-hook.sh\n> +++ b/t/t1800-hook.sh\n> @@ -21,6 +21,11 @@ test_expect_success 'git hook run: nonexistent hook' '\n>  \ttest_cmp stderr.expect stderr.actual\n>  '\n>  \n> +test_expect_success 'git hook run: nonexistent hook with --ignore-missing' '\n> +\tgit hook run --ignore-missing does-not-exist 2>stderr.actual &&\n> +\ttest_must_be_empty stderr.actual\n> +'\n> +\n\nFor completeness, this should include a test for the non-exceptional\ncase i.e. when the hook isn't missing.\n\n>  test_expect_success 'git hook run: basic' '\n>  \twrite_script .git/hooks/test-hook <<-EOF &&\n>  \techo Test hook\n> -- \n> 2.34.1.1146.gb52885e7c44\n\n[1] https://lore.kernel.org/git/cover-00.13-00000000000-20211012T131934Z-avarab@gmail.com/\n"},{"id":"445752","messageId":"Ydi1IHDE7PIybKmB@google.com","threadId":"56717","inReplyTo":"cover-v6-00.17-00000000000-20211222T035755Z-avarab@gmail.com","subject":"Re: [PATCH v6 00/17] hook.[ch]: new library to run hooks + simple hook conversion","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2022-01-07T21:48:16Z","receivedAt":"2022-01-07T21:48:24Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Wed, Dec 22, 2021 at 04:59:26AM +0100, Ævar Arnfjörð Bjarmason wrote:\n> \n> A trivial update to v5 to rebase it past conflicts with topics that\n> recently landed on \"master\". For v5 see:\n> https://lore.kernel.org/git/cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com/\n\nOne note, which is not so useful for this time, but might be useful in\nthe future:\n\nIt seems that in order to understand the purpose of this series and what\nit's building for, you need to be me or be you (and therefore be aware\nof the history of the series), or you need to go back through the\nbreadcrumbs and eventually read my huge config-based-hooks 30-patch\nseries (or at least the cover letter). Which sort of defeats the purpose\nof splitting the series up into smaller reviewable chunks.\n\nNext time it might be nice to add a quick summary of the whole goal,\nwhich part of that goal this series is, and a link to the longer cover\nletter/older series? But again, for this time, I think it is probably\ntoo late, especially since this series looked pretty good to me.\n\nPer-patch reviews to follow momentarily.\n\n - Emily\n\n> \n> Emily Shaffer (14):\n>   hook: add 'run' subcommand\n>   gc: use hook library for pre-auto-gc hook\n>   am: convert {pre,post}-applypatch to use hook.h\n>   rebase: convert pre-rebase to use hook.h\n>   am: convert applypatch-msg to use hook.h\n>   merge: convert post-merge to use hook.h\n>   hooks: convert non-worktree 'post-checkout' hook to hook library\n>   hooks: convert worktree 'post-checkout' hook to hook library\n>   send-email: use 'git hook run' for 'sendemail-validate'\n>   git-p4: use 'git hook' to run hooks\n>   commit: convert {pre-commit,prepare-commit-msg} hook to hook.h\n>   read-cache: convert post-index-change to use hook.h\n>   receive-pack: convert push-to-checkout hook to hook.h\n>   run-command: remove old run_hook_{le,ve}() hook API\n> \n> Ævar Arnfjörð Bjarmason (3):\n>   hook API: add a run_hooks() wrapper\n>   hook API: add a run_hooks_l() wrapper\n>   git hook run: add an --ignore-missing flag\n> \n>  .gitignore                 |   1 +\n>  Documentation/git-hook.txt |  45 +++++++++++++\n>  Documentation/githooks.txt |   4 ++\n>  Makefile                   |   1 +\n>  builtin.h                  |   1 +\n>  builtin/am.c               |   6 +-\n>  builtin/checkout.c         |   3 +-\n>  builtin/clone.c            |   3 +-\n>  builtin/gc.c               |   3 +-\n>  builtin/hook.c             |  84 +++++++++++++++++++++++\n>  builtin/merge.c            |   2 +-\n>  builtin/rebase.c           |   3 +-\n>  builtin/receive-pack.c     |   7 +-\n>  builtin/worktree.c         |  26 +++----\n>  command-list.txt           |   1 +\n>  commit.c                   |  15 +++--\n>  git-p4.py                  |  70 ++-----------------\n>  git-send-email.perl        |  22 +++---\n>  git.c                      |   1 +\n>  hook.c                     | 131 ++++++++++++++++++++++++++++++++++++\n>  hook.h                     |  57 ++++++++++++++++\n>  read-cache.c               |   3 +-\n>  reset.c                    |   3 +-\n>  run-command.c              |  33 ---------\n>  run-command.h              |  17 -----\n>  t/t1800-hook.sh            | 134 +++++++++++++++++++++++++++++++++++++\n>  t/t9001-send-email.sh      |   4 +-\n>  27 files changed, 522 insertions(+), 158 deletions(-)\n>  create mode 100644 Documentation/git-hook.txt\n>  create mode 100644 builtin/hook.c\n>  create mode 100755 t/t1800-hook.sh\n> \n> Range-diff against v5:\n>  1:  4ca52feebb8 =  1:  ba6fd47482e hook: add 'run' subcommand\n>  2:  6275b97a306 =  2:  cfba5c139e7 hook API: add a run_hooks() wrapper\n>  3:  b5b3051b2e5 =  3:  a4cca074bcb gc: use hook library for pre-auto-gc hook\n>  4:  c88eb5d4c25 =  4:  ce57ce1adcb am: convert {pre,post}-applypatch to use hook.h\n>  5:  1d8f7b7e4c1 =  5:  d6162fbef80 hook API: add a run_hooks_l() wrapper\n>  6:  d49a1444345 =  6:  4c1a8951fc5 rebase: convert pre-rebase to use hook.h\n>  7:  191fdad0165 =  7:  d8aa5e8345f am: convert applypatch-msg to use hook.h\n>  8:  119b92fbeae =  8:  6f8d3754b4f merge: convert post-merge to use hook.h\n>  9:  359ba416e84 !  9:  d3107034806 hooks: convert non-worktree 'post-checkout' hook to hook library\n>     @@ builtin/checkout.c\n>       #include \"ll-merge.h\"\n>       #include \"lockfile.h\"\n>       #include \"merge-recursive.h\"\n>     -@@ builtin/checkout.c: struct branch_info {\n>     +@@ builtin/checkout.c: static void branch_info_release(struct branch_info *info)\n>       static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,\n>       \t\t\t      int changed)\n>       {\n> 10:  b7599be95a7 ! 10:  bff7c1513ca hooks: convert worktree 'post-checkout' hook to hook library\n>     @@ builtin/worktree.c: static int add_worktree(const char *path, const char *refnam\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\tstruct child_process cp = CHILD_PROCESS_INIT;\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\tstrvec_pushv(&cp.env_array, env);\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> 11:  f1c84d7f627 = 11:  7d9c0a73568 git hook run: add an --ignore-missing flag\n> 12:  4e0f94d9102 = 12:  8ea3b250dff send-email: use 'git hook run' for 'sendemail-validate'\n> 13:  e858f332a62 = 13:  a184afd1ffd git-p4: use 'git hook' to run hooks\n> 14:  9a5956cc028 = 14:  1a43e50617f commit: convert {pre-commit,prepare-commit-msg} hook to hook.h\n> 15:  6fd47c4c499 = 15:  08b7e63ba5b read-cache: convert post-index-change to use hook.h\n> 16:  b201ea46f4b = 16:  c47b36ab41a receive-pack: convert push-to-checkout hook to hook.h\n> 17:  281d17b04db ! 17:  7b99a4b633c run-command: remove old run_hook_{le,ve}() hook API\n>     @@ run-command.c: int async_with_fork(void)\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>     +-\tif (env)\n>     +-\t\tstrvec_pushv(&hook.env_array, (const char **)env);\n>      -\thook.no_stdin = 1;\n>      -\thook.stdout_to_stderr = 1;\n>      -\thook.trace2_hook_name = name;\n> -- \n> 2.34.1.1146.gb52885e7c44\n> \n"},{"id":"445753","messageId":"Ydi2XEvZywEyImfC@google.com","threadId":"56717","inReplyTo":"patch-v6-01.17-ba6fd47482e-20211222T035755Z-avarab@gmail.com","subject":"Re: [PATCH v6 01/17] hook: add 'run' subcommand","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2022-01-07T21:53:32Z","receivedAt":"2022-01-07T21:53:39Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Wed, Dec 22, 2021 at 04:59:27AM +0100, Ævar Arnfjörð Bjarmason wrote:\n> diff --git a/hook.h b/hook.h\n> +struct run_hooks_opt\n> +{\n> +\t/* Environment vars to be set for each hook */\n> +\tstruct strvec env;\n> +\n> +\t/* Args to be passed to each hook */\n> +\tstruct strvec args;\n> +\n> +\t/* Emit an error if the hook is missing */\n> +\tunsigned int error_if_missing:1;\n\nI wonder if it's premature to include error_if_missing, if we will\nalways set it to 1 until way down in patch 11.\n\nBut I do not care all that much - at this point, I'll be honest, I'd\njust like to see the series merged.\n\n> diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh\n> new file mode 100755\n> +test_expect_success 'git hook run: nonexistent hook' '\n> +\tcat >stderr.expect <<-\\EOF &&\n> +\terror: cannot find a hook named test-hook\n> +\tEOF\n> +\ttest_expect_code 1 git hook run test-hook 2>stderr.actual &&\n> +\ttest_cmp stderr.expect stderr.actual\n> +'\n\nIt's a little unclear to me what \"nonexistent\" means - does it mean that\nit's a hook unknown to Git (e.g. not in hook-list.h)? Does it mean that\nthe hook path doesn't exist? Does it mean the hook path is not\nexecutable?\n\nI had to poke in the code a little bit but it looks like this whole\nknown_hook() distinction has disappeared, so maybe I only find it\nconfusing because I reviewed previous iterations.\n\nIt might be nice to at least leave a comment on this test case and point\nout that it relies on nobody having created a test-hook script in an\nearlier test. Or to clean up that path just in case, at the beginning of\nthis one. I could see someone well-meaning adding another test before\nthis for something simple and breaking this test.\n\n\nNo serious concerns here though - just a couple thoughts.\n\n - Emily\n"},{"id":"445754","messageId":"Ydi2/89zaLnsFc/Y@google.com","threadId":"56717","inReplyTo":"patch-v6-05.17-d6162fbef80-20211222T035755Z-avarab@gmail.com","subject":"Re: [PATCH v6 05/17] hook API: add a run_hooks_l() wrapper","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2022-01-07T21:56:15Z","receivedAt":"2022-01-07T21:56:23Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Wed, Dec 22, 2021 at 04:59:31AM +0100, Ævar Arnfjörð Bjarmason wrote:\n> \n> Add a run_hooks_l() wrapper, we'll use it in subsequent commits for\n> the simple cases of wanting to run a single hook under a given name\n> along with a list of arguments.\n\nI've got a slight allergy to varargs, just as personal preference, but\nthere's no denying that this makes the following callsite conversions\nextremely mechanical and easy to review.\n\nDoes this calling convention make it easier for us to try and inline\nresults from something like oid_to_hex(), which may become stale,\ncompared to needing to put together a struct with args? I think probably\nit's fine - because strvec_push() creates a copy, and run_hooks_l() is\nnot something we'd want to call multiple times in parallel (I hope).\n\nAnyway,\nReviewed-by: Emily Shaffer <emilyshaffer@google.com>\n\n> \n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> ---\n>  hook.c | 14 ++++++++++++++\n>  hook.h | 10 ++++++++++\n>  2 files changed, 24 insertions(+)\n> \n> diff --git a/hook.c b/hook.c\n> index d67a114e62d..1ad123422b2 100644\n> --- a/hook.c\n> +++ b/hook.c\n> @@ -149,3 +149,17 @@ int run_hooks(const char *hook_name)\n>  \n>  \treturn run_hooks_opt(hook_name, &opt);\n>  }\n> +\n> +int run_hooks_l(const char *hook_name, ...)\n> +{\n> +\tstruct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;\n> +\tva_list ap;\n> +\tconst char *arg;\n> +\n> +\tva_start(ap, hook_name);\n> +\twhile ((arg = va_arg(ap, const char *)))\n> +\t\tstrvec_push(&opt.args, arg);\n> +\tva_end(ap);\n> +\n> +\treturn run_hooks_opt(hook_name, &opt);\n> +}\n> diff --git a/hook.h b/hook.h\n> index 9c358789958..54528395953 100644\n> --- a/hook.h\n> +++ b/hook.h\n> @@ -54,4 +54,14 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);\n>   * run_hooks_opt\" initialized with \"RUN_HOOKS_OPT_INIT\".\n>   */\n>  int run_hooks(const char *hook_name);\n> +\n> +/**\n> + * Like run_hooks(), a wrapper for run_hooks_opt().\n> + *\n> + * In addition to the wrapping behavior provided by run_hooks(), this\n> + * wrapper takes a list of strings terminated by a NULL\n> + * argument. These things will be used as positional arguments to the\n> + * hook. This function behaves like the old run_hook_le() API.\n> + */\n> +int run_hooks_l(const char *hook_name, ...);\n>  #endif\n> -- \n> 2.34.1.1146.gb52885e7c44\n> \n"},{"id":"445755","messageId":"Ydi4LbluVDTfaNGG@google.com","threadId":"56717","inReplyTo":"Ydi1IHDE7PIybKmB@google.com","subject":"Re: [PATCH v6 00/17] hook.[ch]: new library to run hooks + simple hook conversion","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2022-01-07T22:01:17Z","receivedAt":"2022-01-07T22:01:25Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Fri, Jan 07, 2022 at 01:48:16PM -0800, Emily Shaffer wrote:\n> \n> On Wed, Dec 22, 2021 at 04:59:26AM +0100, Ævar Arnfjörð Bjarmason wrote:\n> > \n> > A trivial update to v5 to rebase it past conflicts with topics that\n> > recently landed on \"master\". For v5 see:\n> > https://lore.kernel.org/git/cover-v5-00.17-00000000000-20211123T114206Z-avarab@gmail.com/\n> \n> One note, which is not so useful for this time, but might be useful in\n> the future:\n> \n> It seems that in order to understand the purpose of this series and what\n> it's building for, you need to be me or be you (and therefore be aware\n> of the history of the series), or you need to go back through the\n> breadcrumbs and eventually read my huge config-based-hooks 30-patch\n> series (or at least the cover letter). Which sort of defeats the purpose\n> of splitting the series up into smaller reviewable chunks.\n> \n> Next time it might be nice to add a quick summary of the whole goal,\n> which part of that goal this series is, and a link to the longer cover\n> letter/older series? But again, for this time, I think it is probably\n> too late, especially since this series looked pretty good to me.\n> \n> Per-patch reviews to follow momentarily.\n\nLeft a couple nits here and there, but it all looks fine for me. Some\ncomments that I thought I had turned out to be invalid once I read the\nseries a second time... ;)\n\nFor all patches, but especially Ævar's,\nReviewed-by: Emily Shaffer <emilyshaffer@google.com>\n\nI think that's stronger than an ACK for mine, but either way, for my\nown patches:\nAcked-by: Emily Shaffer <emilyshaffer@google.com>\n\nThanks. I'd be excited to see this go in.\n\n - Emily\n"},{"id":"445760","messageId":"xmqqfspz5ekc.fsf@gitster.g","threadId":"56717","inReplyTo":"Ydi2XEvZywEyImfC@google.com","subject":"Re: [PATCH v6 01/17] hook: add 'run' subcommand","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-07T23:08:51Z","receivedAt":"2022-01-07T23:08:55Z","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> On Wed, Dec 22, 2021 at 04:59:27AM +0100, Ævar Arnfjörð Bjarmason wrote:\n>> diff --git a/hook.h b/hook.h\n>> +struct run_hooks_opt\n>> +{\n>> +\t/* Environment vars to be set for each hook */\n>> +\tstruct strvec env;\n>> +\n>> +\t/* Args to be passed to each hook */\n>> +\tstruct strvec args;\n>> +\n>> +\t/* Emit an error if the hook is missing */\n>> +\tunsigned int error_if_missing:1;\n>\n> I wonder if it's premature to include error_if_missing, if we will\n> always set it to 1 until way down in patch 11.\n>\n> But I do not care all that much - at this point, I'll be honest, I'd\n> just like to see the series merged.\n>\n>> diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh\n>> new file mode 100755\n>> +test_expect_success 'git hook run: nonexistent hook' '\n>> +\tcat >stderr.expect <<-\\EOF &&\n>> +\terror: cannot find a hook named test-hook\n>> +\tEOF\n>> +\ttest_expect_code 1 git hook run test-hook 2>stderr.actual &&\n>> +\ttest_cmp stderr.expect stderr.actual\n>> +'\n>\n> It's a little unclear to me what \"nonexistent\" means - does it mean that\n> it's a hook unknown to Git (e.g. not in hook-list.h)? Does it mean that\n> the hook path doesn't exist? Does it mean the hook path is not\n> executable?\n\nI think I saw the same puzzlement from another reviewer.  It needs\nto be clarified if the documentation patch does not do a good job.\n\n> It might be nice to at least leave a comment on this test case and point\n> out that it relies on nobody having created a test-hook script in an\n> earlier test. Or to clean up that path just in case, at the beginning of\n> this one.\n\nYup, I agree that the latter is a good practice.\n\n"}]}