git@vger.kernel.org list mirror (unofficial, one of many)
 help / color / mirror / code / Atom feed
* [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion
@ 2021-10-15  9:43 Ævar Arnfjörð Bjarmason
  2021-10-15  9:43 ` [PATCH v2 01/13] hook: add 'run' subcommand Ævar Arnfjörð Bjarmason
                   ` (15 more replies)
  0 siblings, 16 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-15  9:43 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

Part 2 of the greater configurable hook saga, starting by converting
some existing simple hooks to the new hook.[ch] library and "git hook
run" utility.

See the v1 [1] CL for more context. I've dropped the git-p4 folks from
the CC list, your feedback on git-p4 would still be very appreciated,
but I don't think I need to re-spam you for every re-roll of this.

This v2:

 * Should address all outstanding comments on v1, which were mainly of
   the form that some boilerplate here didn't reflect the actual state
   of the code.

   I've updated the docs, code etc. to 1=1 map to the existing
   behavior. I said I'd punt on e.g. the one-member "struct hook" in
   reply to René Scharfe, but when I tried it and rebased the rest on
   it it actually made the progression more understandable, so I've
   done that here.

   I didn't convert some things, which are noted in the updated commit
   message on v1, e.g. we still use the parallel run-command.c API
   with jobs=1, and have a function called run_hooks() that only ever
   runs one hook.

1. https://lore.kernel.org/git/cover-00.13-00000000000-20211012T131934Z-avarab@gmail.com/

Emily Shaffer (12):
  hook: add 'run' subcommand
  gc: use hook library for pre-auto-gc hook
  rebase: convert pre-rebase to use hook.h
  am: convert applypatch to use hook.h
  hooks: convert 'post-checkout' hook to hook library
  merge: convert post-merge to use hook.h
  send-email: use 'git hook run' for 'sendemail-validate'
  git-p4: use 'git hook' to run hooks
  commit: convert {pre-commit,prepare-commit-msg} hook to hook.h
  read-cache: convert post-index-change to use hook.h
  receive-pack: convert push-to-checkout hook to hook.h
  run-command: remove old run_hook_{le,ve}() hook API

Ævar Arnfjörð Bjarmason (1):
  git hook run: add an --ignore-missing flag

 .gitignore                 |   1 +
 Documentation/git-hook.txt |  45 +++++++++++++
 Documentation/githooks.txt |   4 ++
 Makefile                   |   1 +
 builtin.h                  |   1 +
 builtin/am.c               |   8 ++-
 builtin/checkout.c         |  14 ++--
 builtin/clone.c            |   7 +-
 builtin/gc.c               |   3 +-
 builtin/hook.c             |  90 +++++++++++++++++++++++++
 builtin/merge.c            |   4 +-
 builtin/rebase.c           |   8 ++-
 builtin/receive-pack.c     |   7 +-
 builtin/worktree.c         |  28 ++++----
 command-list.txt           |   1 +
 commit.c                   |  15 +++--
 git-p4.py                  |  72 ++------------------
 git-send-email.perl        |  22 +++---
 git.c                      |   1 +
 hook.c                     | 121 +++++++++++++++++++++++++++++++++
 hook.h                     |  56 ++++++++++++++++
 read-cache.c               |  11 ++-
 reset.c                    |  14 ++--
 run-command.c              |  32 ---------
 run-command.h              |  17 -----
 t/t1800-hook.sh            | 134 +++++++++++++++++++++++++++++++++++++
 t/t9001-send-email.sh      |   4 +-
 27 files changed, 551 insertions(+), 170 deletions(-)
 create mode 100644 Documentation/git-hook.txt
 create mode 100644 builtin/hook.c
 create mode 100755 t/t1800-hook.sh

Range-diff against v1:
 1:  a39c0748d3f !  1:  ba64faf0580 hook: add 'run' subcommand
    @@ Commit message
         pattern here mirrors that of
         builtin/{commit-graph,multi-pack-index}.c.
     
    +    Some of the implementation here, such as a function being named
    +    run_hooks() when it's tasked with running one hook, to using the
    +    run_processes_parallel_tr2() API to run with jobs=1 is somewhere
    +    between a bit odd and and an overkill for the current features of this
    +    "hook run" command and the hook.[ch] API.
    +
    +    This code will eventually be able to run multiple hooks declared in
    +    config in parallel, by starting out with these names and APIs we
    +    reduce the later churn of renaming functions, switching from the
    +    run_command() to run_processes_parallel_tr2() API etc.
    +
         Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
    @@ Documentation/git-hook.txt (new)
     +
     +NAME
     +----
    -+git-hook - run git hooks
    ++git-hook - Run git hooks
     +
     +SYNOPSIS
     +--------
    @@ Documentation/git-hook.txt (new)
     +DESCRIPTION
     +-----------
     +
    -+This command is an interface to git hooks (see linkgit:githooks[5]).
    -+Currently it only provides a convenience wrapper for running hooks for
    -+use by git itself. In the future it might gain other functionality.
    ++A command interface to running git hooks (see linkgit:githooks[5]),
    ++for use by other scripted git commands.
     +
     +SUBCOMMANDS
     +-----------
     +
     +run::
     +	Run the `<hook-name>` hook. See linkgit:githooks[5] for
    -+	the hook names we support.
    ++	supported hook names.
     ++
    -+Any positional arguments to the hook should be passed after an
    -+optional `--` (or `--end-of-options`, see linkgit:gitcli[7]). The
    -+arguments (if any) differ by hook name, see linkgit:githooks[5] for
    -+what those are.
    ++
    ++Any positional arguments to the hook should be passed after a
    ++mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See
    ++linkgit:githooks[5] for arguments hooks might expect (if any).
     +
     +SEE ALSO
     +--------
    @@ builtin/hook.c (new)
     +	/* Need to take into account core.hooksPath */
     +	git_config(git_default_config, NULL);
     +
    -+	/*
    -+	 * We are not using a plain run_hooks() because we'd like to
    -+	 * detect missing hooks. Let's find it ourselves and call
    -+	 * run_hooks() instead.
    -+	 */
     +	hook_name = argv[0];
     +	hook_path = find_hook(hook_name);
     +	if (!hook_path) {
    @@ command-list.txt: git-grep                                mainporcelain
      git-gui                                 mainporcelain
      git-hash-object                         plumbingmanipulators
      git-help                                ancillaryinterrogators          complete
    -+git-hook                                mainporcelain
    ++git-hook                                purehelpers
      git-http-backend                        synchingrepositories
      git-http-fetch                          synchelpers
      git-http-push                           synchelpers
    @@ hook.c: int hook_exists(const char *name)
     +			  void **pp_task_cb)
     +{
     +	struct hook_cb_data *hook_cb = pp_cb;
    -+	struct hook *run_me = hook_cb->run_me;
    ++	const char *hook_path = hook_cb->hook_path;
     +
    -+	if (!run_me)
    ++	if (!hook_path)
     +		return 0;
     +
     +	cp->no_stdin = 1;
    @@ hook.c: int hook_exists(const char *name)
     +	cp->stdout_to_stderr = 1;
     +	cp->trace2_hook_name = hook_cb->hook_name;
     +
    -+	/* add command */
    -+	strvec_push(&cp->args, run_me->hook_path);
    -+
    -+	/*
    -+	 * add passed-in argv, without expanding - let the user get back
    -+	 * exactly what they put in
    -+	 */
    ++	strvec_push(&cp->args, hook_path);
     +	strvec_pushv(&cp->args, hook_cb->options->args.v);
     +
     +	/* Provide context for errors if necessary */
    -+	*pp_task_cb = run_me;
    ++	*pp_task_cb = (char *)hook_path;
     +
     +	/*
     +	 * This pick_next_hook() will be called again, we're only
     +	 * running one hook, so indicate that no more work will be
     +	 * done.
     +	 */
    -+	hook_cb->run_me = NULL;
    ++	hook_cb->hook_path = NULL;
     +
     +	return 1;
     +}
    @@ hook.c: int hook_exists(const char *name)
     +				void *pp_task_cp)
     +{
     +	struct hook_cb_data *hook_cb = pp_cb;
    -+	struct hook *attempted = pp_task_cp;
    ++	const char *hook_path = pp_task_cp;
     +
     +	hook_cb->rc |= 1;
     +
     +	strbuf_addf(out, _("Couldn't start hook '%s'\n"),
    -+		    attempted->hook_path);
    ++		    hook_path);
     +
     +	return 1;
     +}
    @@ hook.c: int hook_exists(const char *name)
     +int run_hooks(const char *hook_name, const char *hook_path,
     +	      struct run_hooks_opt *options)
     +{
    -+	struct hook my_hook = {
    -+		.hook_path = hook_path,
    -+	};
     +	struct hook_cb_data cb_data = {
     +		.rc = 0,
     +		.hook_name = hook_name,
    ++		.hook_path = hook_path,
     +		.options = options,
     +	};
     +	int jobs = 1;
    @@ hook.c: int hook_exists(const char *name)
     +	if (!options)
     +		BUG("a struct run_hooks_opt must be provided to run_hooks");
     +
    -+	cb_data.run_me = &my_hook;
    -+
     +	run_processes_parallel_tr2(jobs,
     +				   pick_next_hook,
     +				   notify_start_failure,
    @@ hook.h
      #define HOOK_H
     +#include "strvec.h"
     +
    -+struct hook {
    -+	/* The path to the hook */
    -+	const char *hook_path;
    -+};
    -+
     +struct run_hooks_opt
     +{
     +	/* Environment vars to be set for each hook */
    @@ hook.h
     +	/* rc reflects the cumulative failure state */
     +	int rc;
     +	const char *hook_name;
    -+	struct hook *run_me;
    ++	const char *hook_path;
     +	struct run_hooks_opt *options;
     +};
      
    @@ hook.h: const char *find_hook(const char *name);
      int hook_exists(const char *hookname);
      
     +/**
    -+ * Clear data from an initialized "struct run_hooks-opt".
    ++ * Clear data from an initialized "struct run_hooks_opt".
     + */
     +void run_hooks_opt_clear(struct run_hooks_opt *o);
     +
 2:  dbac4204f7b =  2:  e3dc0aed81b gc: use hook library for pre-auto-gc hook
 3:  ff306debcb8 =  3:  6227a1e644d rebase: convert pre-rebase to use hook.h
 4:  b1d529ca485 =  4:  0e34eb54054 am: convert applypatch to use hook.h
 5:  15d71fc210b !  5:  a4df96c1719 hooks: convert 'post-checkout' hook to hook library
    @@ hook.c: static int pick_next_hook(struct child_process *cp,
      	cp->trace2_hook_name = hook_cb->hook_name;
     +	cp->dir = hook_cb->options->dir;
      
    - 	/* add command */
    - 	strvec_push(&cp->args, run_me->hook_path);
    + 	strvec_push(&cp->args, hook_path);
    + 	strvec_pushv(&cp->args, hook_cb->options->args.v);
     @@ hook.c: static int notify_hook_finished(int result,
      int run_hooks(const char *hook_name, const char *hook_path,
      	      struct run_hooks_opt *options)
      {
     +	struct strbuf abs_path = STRBUF_INIT;
    - 	struct hook my_hook = {
    - 		.hook_path = hook_path,
    - 	};
    + 	struct hook_cb_data cb_data = {
    + 		.rc = 0,
    + 		.hook_name = hook_name,
     @@ hook.c: int run_hooks(const char *hook_name, const char *hook_path,
      	if (!options)
      		BUG("a struct run_hooks_opt must be provided to run_hooks");
      
     +	if (options->absolute_path) {
     +		strbuf_add_absolute_path(&abs_path, hook_path);
    -+		my_hook.hook_path = abs_path.buf;
    ++		hook_path = abs_path.buf;
     +	}
    - 	cb_data.run_me = &my_hook;
    - 
    ++
      	run_processes_parallel_tr2(jobs,
    + 				   pick_next_hook,
    + 				   notify_start_failure,
     @@ hook.c: int run_hooks(const char *hook_name, const char *hook_path,
      				   "hook",
      				   hook_name);
 6:  08f27f0d6be =  6:  327f916f8c3 merge: convert post-merge to use hook.h
 7:  107c14d740f !  7:  328767015b1 git hook run: add an --ignore-missing flag
    @@ Commit message
         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
      ## Documentation/git-hook.txt ##
    -@@ Documentation/git-hook.txt: git-hook - run git hooks
    +@@ Documentation/git-hook.txt: git-hook - Run git hooks
      SYNOPSIS
      --------
      [verse]
    @@ Documentation/git-hook.txt: git-hook - run git hooks
      
      DESCRIPTION
      -----------
    -@@ Documentation/git-hook.txt: optional `--` (or `--end-of-options`, see linkgit:gitcli[7]). The
    - arguments (if any) differ by hook name, see linkgit:githooks[5] for
    - what those are.
    +@@ Documentation/git-hook.txt: Any positional arguments to the hook should be passed after a
    + mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See
    + linkgit:githooks[5] for arguments hooks might expect (if any).
      
     +OPTIONS
     +-------
    @@ builtin/hook.c: static int run(int argc, const char **argv, const char *prefix)
      	};
      	int ret;
     @@ builtin/hook.c: static int run(int argc, const char **argv, const char *prefix)
    - 	/*
    - 	 * We are not using a plain run_hooks() because we'd like to
    - 	 * detect missing hooks. Let's find it ourselves and call
    --	 * run_hooks() instead.
    -+	 * run_hooks() instead...
    - 	 */
    + 	git_config(git_default_config, NULL);
    + 
      	hook_name = argv[0];
     +	if (ignore_missing)
    -+		/* ... act like a plain run_hooks() under --ignore-missing */
     +		return run_hooks_oneshot(hook_name, &opt);
      	hook_path = find_hook(hook_name);
      	if (!hook_path) {
 8:  1d30e2dbbe0 =  8:  6c4ebd68d56 send-email: use 'git hook run' for 'sendemail-validate'
 9:  69cc447a1e1 =  9:  b1f52733e3c git-p4: use 'git hook' to run hooks
10:  1c22b2992cf = 10:  dc31d98acdf commit: convert {pre-commit,prepare-commit-msg} hook to hook.h
11:  e762fce32af = 11:  58b7689e4af read-cache: convert post-index-change to use hook.h
12:  d63b91196ae = 12:  ae1e2a82147 receive-pack: convert push-to-checkout hook to hook.h
13:  fe8996dda3e = 13:  289d5a2d849 run-command: remove old run_hook_{le,ve}() hook API
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v2 01/13] hook: add 'run' subcommand
  2021-10-15  9:43 [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
@ 2021-10-15  9:43 ` Ævar Arnfjörð Bjarmason
  2021-10-15 16:24   ` Emily Shaffer
  2021-10-15  9:43 ` [PATCH v2 02/13] gc: use hook library for pre-auto-gc hook Ævar Arnfjörð Bjarmason
                   ` (14 subsequent siblings)
  15 siblings, 1 reply; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-15  9:43 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

In order to enable hooks to be run as an external process, by a
standalone Git command, or by tools which wrap Git, provide an external
means to run all configured hook commands for a given hook event.

Most of our hooks require more complex functionality than this, but
let's start with the bare minimum required to support our simplest
hooks.

In terms of implementation the usage_with_options() and "goto usage"
pattern here mirrors that of
builtin/{commit-graph,multi-pack-index}.c.

Some of the implementation here, such as a function being named
run_hooks() when it's tasked with running one hook, to using the
run_processes_parallel_tr2() API to run with jobs=1 is somewhere
between a bit odd and and an overkill for the current features of this
"hook run" command and the hook.[ch] API.

This code will eventually be able to run multiple hooks declared in
config in parallel, by starting out with these names and APIs we
reduce the later churn of renaming functions, switching from the
run_command() to run_processes_parallel_tr2() API etc.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 .gitignore                 |   1 +
 Documentation/git-hook.txt |  37 +++++++++++
 Documentation/githooks.txt |   4 ++
 Makefile                   |   1 +
 builtin.h                  |   1 +
 builtin/hook.c             |  85 ++++++++++++++++++++++++
 command-list.txt           |   1 +
 git.c                      |   1 +
 hook.c                     |  91 ++++++++++++++++++++++++++
 hook.h                     |  34 ++++++++++
 t/t1800-hook.sh            | 129 +++++++++++++++++++++++++++++++++++++
 11 files changed, 385 insertions(+)
 create mode 100644 Documentation/git-hook.txt
 create mode 100644 builtin/hook.c
 create mode 100755 t/t1800-hook.sh

diff --git a/.gitignore b/.gitignore
index 80c6bf6f3a8..005be1415ed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -77,6 +77,7 @@
 /git-grep
 /git-hash-object
 /git-help
+/git-hook
 /git-http-backend
 /git-http-fetch
 /git-http-push
diff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt
new file mode 100644
index 00000000000..e39b1b5d069
--- /dev/null
+++ b/Documentation/git-hook.txt
@@ -0,0 +1,37 @@
+git-hook(1)
+===========
+
+NAME
+----
+git-hook - Run git hooks
+
+SYNOPSIS
+--------
+[verse]
+'git hook' run <hook-name> [-- <hook-args>]
+
+DESCRIPTION
+-----------
+
+A command interface to running git hooks (see linkgit:githooks[5]),
+for use by other scripted git commands.
+
+SUBCOMMANDS
+-----------
+
+run::
+	Run the `<hook-name>` hook. See linkgit:githooks[5] for
+	supported hook names.
++
+
+Any positional arguments to the hook should be passed after a
+mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See
+linkgit:githooks[5] for arguments hooks might expect (if any).
+
+SEE ALSO
+--------
+linkgit:githooks[5]
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index b51959ff941..a16e62bc8c8 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -698,6 +698,10 @@ and "0" meaning they were not.
 Only one parameter should be set to "1" when the hook runs.  The hook
 running passing "1", "1" should not be possible.
 
+SEE ALSO
+--------
+linkgit:git-hook[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/Makefile b/Makefile
index 060a8c46475..c95997f8d21 100644
--- a/Makefile
+++ b/Makefile
@@ -1108,6 +1108,7 @@ BUILTIN_OBJS += builtin/get-tar-commit-id.o
 BUILTIN_OBJS += builtin/grep.o
 BUILTIN_OBJS += builtin/hash-object.o
 BUILTIN_OBJS += builtin/help.o
+BUILTIN_OBJS += builtin/hook.o
 BUILTIN_OBJS += builtin/index-pack.o
 BUILTIN_OBJS += builtin/init-db.o
 BUILTIN_OBJS += builtin/interpret-trailers.o
diff --git a/builtin.h b/builtin.h
index 8a58743ed63..83379f3832c 100644
--- a/builtin.h
+++ b/builtin.h
@@ -164,6 +164,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);
 int cmd_grep(int argc, const char **argv, const char *prefix);
 int cmd_hash_object(int argc, const char **argv, const char *prefix);
 int cmd_help(int argc, const char **argv, const char *prefix);
+int cmd_hook(int argc, const char **argv, const char *prefix);
 int cmd_index_pack(int argc, const char **argv, const char *prefix);
 int cmd_init_db(int argc, const char **argv, const char *prefix);
 int cmd_interpret_trailers(int argc, const char **argv, const char *prefix);
diff --git a/builtin/hook.c b/builtin/hook.c
new file mode 100644
index 00000000000..41dd15550cf
--- /dev/null
+++ b/builtin/hook.c
@@ -0,0 +1,85 @@
+#include "cache.h"
+#include "builtin.h"
+#include "config.h"
+#include "hook.h"
+#include "parse-options.h"
+#include "strbuf.h"
+#include "strvec.h"
+
+#define BUILTIN_HOOK_RUN_USAGE \
+	N_("git hook run <hook-name> [-- <hook-args>]")
+
+static const char * const builtin_hook_usage[] = {
+	BUILTIN_HOOK_RUN_USAGE,
+	NULL
+};
+
+static const char * const builtin_hook_run_usage[] = {
+	BUILTIN_HOOK_RUN_USAGE,
+	NULL
+};
+
+static int run(int argc, const char **argv, const char *prefix)
+{
+	int i;
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+	const char *hook_name;
+	const char *hook_path;
+	struct option run_options[] = {
+		OPT_END(),
+	};
+	int ret;
+
+	argc = parse_options(argc, argv, prefix, run_options,
+			     builtin_hook_run_usage,
+			     PARSE_OPT_KEEP_DASHDASH);
+
+	if (!argc)
+		goto usage;
+
+	/*
+	 * Having a -- for "run" when providing <hook-args> is
+	 * mandatory.
+	 */
+	if (argc > 1 && strcmp(argv[1], "--") &&
+	    strcmp(argv[1], "--end-of-options"))
+		goto usage;
+
+	/* Add our arguments, start after -- */
+	for (i = 2 ; i < argc; i++)
+		strvec_push(&opt.args, argv[i]);
+
+	/* Need to take into account core.hooksPath */
+	git_config(git_default_config, NULL);
+
+	hook_name = argv[0];
+	hook_path = find_hook(hook_name);
+	if (!hook_path) {
+		error("cannot find a hook named %s", hook_name);
+		return 1;
+	}
+
+	ret = run_hooks(hook_name, hook_path, &opt);
+	run_hooks_opt_clear(&opt);
+	return ret;
+usage:
+	usage_with_options(builtin_hook_run_usage, run_options);
+}
+
+int cmd_hook(int argc, const char **argv, const char *prefix)
+{
+	struct option builtin_hook_options[] = {
+		OPT_END(),
+	};
+
+	argc = parse_options(argc, argv, NULL, builtin_hook_options,
+			     builtin_hook_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+	if (!argc)
+		goto usage;
+
+	if (!strcmp(argv[0], "run"))
+		return run(argc, argv, prefix);
+
+usage:
+	usage_with_options(builtin_hook_usage, builtin_hook_options);
+}
diff --git a/command-list.txt b/command-list.txt
index a289f09ed6f..17c3958802e 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -103,6 +103,7 @@ git-grep                                mainporcelain           info
 git-gui                                 mainporcelain
 git-hash-object                         plumbingmanipulators
 git-help                                ancillaryinterrogators          complete
+git-hook                                purehelpers
 git-http-backend                        synchingrepositories
 git-http-fetch                          synchelpers
 git-http-push                           synchelpers
diff --git a/git.c b/git.c
index 60c2784be45..e5891e02021 100644
--- a/git.c
+++ b/git.c
@@ -538,6 +538,7 @@ static struct cmd_struct commands[] = {
 	{ "grep", cmd_grep, RUN_SETUP_GENTLY },
 	{ "hash-object", cmd_hash_object },
 	{ "help", cmd_help },
+	{ "hook", cmd_hook, RUN_SETUP },
 	{ "index-pack", cmd_index_pack, RUN_SETUP_GENTLY | NO_PARSEOPT },
 	{ "init", cmd_init_db },
 	{ "init-db", cmd_init_db },
diff --git a/hook.c b/hook.c
index 55e1145a4b7..20a1a4fec33 100644
--- a/hook.c
+++ b/hook.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "hook.h"
 #include "run-command.h"
+#include "config.h"
 
 const char *find_hook(const char *name)
 {
@@ -40,3 +41,93 @@ int hook_exists(const char *name)
 {
 	return !!find_hook(name);
 }
+
+void run_hooks_opt_clear(struct run_hooks_opt *o)
+{
+	strvec_clear(&o->env);
+	strvec_clear(&o->args);
+}
+
+static int pick_next_hook(struct child_process *cp,
+			  struct strbuf *out,
+			  void *pp_cb,
+			  void **pp_task_cb)
+{
+	struct hook_cb_data *hook_cb = pp_cb;
+	const char *hook_path = hook_cb->hook_path;
+
+	if (!hook_path)
+		return 0;
+
+	cp->no_stdin = 1;
+	cp->env = hook_cb->options->env.v;
+	cp->stdout_to_stderr = 1;
+	cp->trace2_hook_name = hook_cb->hook_name;
+
+	strvec_push(&cp->args, hook_path);
+	strvec_pushv(&cp->args, hook_cb->options->args.v);
+
+	/* Provide context for errors if necessary */
+	*pp_task_cb = (char *)hook_path;
+
+	/*
+	 * This pick_next_hook() will be called again, we're only
+	 * running one hook, so indicate that no more work will be
+	 * done.
+	 */
+	hook_cb->hook_path = NULL;
+
+	return 1;
+}
+
+static int notify_start_failure(struct strbuf *out,
+				void *pp_cb,
+				void *pp_task_cp)
+{
+	struct hook_cb_data *hook_cb = pp_cb;
+	const char *hook_path = pp_task_cp;
+
+	hook_cb->rc |= 1;
+
+	strbuf_addf(out, _("Couldn't start hook '%s'\n"),
+		    hook_path);
+
+	return 1;
+}
+
+static int notify_hook_finished(int result,
+				struct strbuf *out,
+				void *pp_cb,
+				void *pp_task_cb)
+{
+	struct hook_cb_data *hook_cb = pp_cb;
+
+	hook_cb->rc |= result;
+
+	return 0;
+}
+
+int run_hooks(const char *hook_name, const char *hook_path,
+	      struct run_hooks_opt *options)
+{
+	struct hook_cb_data cb_data = {
+		.rc = 0,
+		.hook_name = hook_name,
+		.hook_path = hook_path,
+		.options = options,
+	};
+	int jobs = 1;
+
+	if (!options)
+		BUG("a struct run_hooks_opt must be provided to run_hooks");
+
+	run_processes_parallel_tr2(jobs,
+				   pick_next_hook,
+				   notify_start_failure,
+				   notify_hook_finished,
+				   &cb_data,
+				   "hook",
+				   hook_name);
+
+	return cb_data.rc;
+}
diff --git a/hook.h b/hook.h
index 6aa36fc7ff9..a9317c3f95e 100644
--- a/hook.h
+++ b/hook.h
@@ -1,5 +1,28 @@
 #ifndef HOOK_H
 #define HOOK_H
+#include "strvec.h"
+
+struct run_hooks_opt
+{
+	/* Environment vars to be set for each hook */
+	struct strvec env;
+
+	/* Args to be passed to each hook */
+	struct strvec args;
+};
+
+#define RUN_HOOKS_OPT_INIT { \
+	.env = STRVEC_INIT, \
+	.args = STRVEC_INIT, \
+}
+
+struct hook_cb_data {
+	/* rc reflects the cumulative failure state */
+	int rc;
+	const char *hook_name;
+	const char *hook_path;
+	struct run_hooks_opt *options;
+};
 
 /*
  * Returns the path to the hook file, or NULL if the hook is missing
@@ -13,4 +36,15 @@ const char *find_hook(const char *name);
  */
 int hook_exists(const char *hookname);
 
+/**
+ * Clear data from an initialized "struct run_hooks_opt".
+ */
+void run_hooks_opt_clear(struct run_hooks_opt *o);
+
+/**
+ * Takes an already resolved hook found via find_hook() and runs
+ * it. Does not call run_hooks_opt_clear() for you.
+ */
+int run_hooks(const char *hookname, const char *hook_path,
+	      struct run_hooks_opt *options);
 #endif
diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh
new file mode 100755
index 00000000000..3aea1b105f0
--- /dev/null
+++ b/t/t1800-hook.sh
@@ -0,0 +1,129 @@
+#!/bin/sh
+
+test_description='git-hook command'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+
+test_expect_success 'git hook usage' '
+	test_expect_code 129 git hook &&
+	test_expect_code 129 git hook run &&
+	test_expect_code 129 git hook run -h &&
+	test_expect_code 129 git hook run --unknown 2>err &&
+	grep "unknown option" err
+'
+
+test_expect_success 'git hook run: nonexistent hook' '
+	cat >stderr.expect <<-\EOF &&
+	error: cannot find a hook named test-hook
+	EOF
+	test_expect_code 1 git hook run test-hook 2>stderr.actual &&
+	test_cmp stderr.expect stderr.actual
+'
+
+test_expect_success 'git hook run: basic' '
+	write_script .git/hooks/test-hook <<-EOF &&
+	echo Test hook
+	EOF
+
+	cat >expect <<-\EOF &&
+	Test hook
+	EOF
+	git hook run test-hook 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git hook run: stdout and stderr both write to our stderr' '
+	write_script .git/hooks/test-hook <<-EOF &&
+	echo >&1 Will end up on stderr
+	echo >&2 Will end up on stderr
+	EOF
+
+	cat >stderr.expect <<-\EOF &&
+	Will end up on stderr
+	Will end up on stderr
+	EOF
+	git hook run test-hook >stdout.actual 2>stderr.actual &&
+	test_cmp stderr.expect stderr.actual &&
+	test_must_be_empty stdout.actual
+'
+
+test_expect_success 'git hook run: exit codes are passed along' '
+	write_script .git/hooks/test-hook <<-EOF &&
+	exit 1
+	EOF
+
+	test_expect_code 1 git hook run test-hook &&
+
+	write_script .git/hooks/test-hook <<-EOF &&
+	exit 2
+	EOF
+
+	test_expect_code 2 git hook run test-hook &&
+
+	write_script .git/hooks/test-hook <<-EOF &&
+	exit 128
+	EOF
+
+	test_expect_code 128 git hook run test-hook &&
+
+	write_script .git/hooks/test-hook <<-EOF &&
+	exit 129
+	EOF
+
+	test_expect_code 129 git hook run test-hook
+'
+
+test_expect_success 'git hook run arg u ments without -- is not allowed' '
+	test_expect_code 129 git hook run test-hook arg u ments
+'
+
+test_expect_success 'git hook run -- pass arguments' '
+	write_script .git/hooks/test-hook <<-\EOF &&
+	echo $1
+	echo $2
+	EOF
+
+	cat >expect <<-EOF &&
+	arg
+	u ments
+	EOF
+
+	git hook run test-hook -- arg "u ments" 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git hook run -- out-of-repo runs excluded' '
+	write_script .git/hooks/test-hook <<-EOF &&
+	echo Test hook
+	EOF
+
+	nongit test_must_fail git hook run test-hook
+'
+
+test_expect_success 'git -c core.hooksPath=<PATH> hook run' '
+	mkdir my-hooks &&
+	write_script my-hooks/test-hook <<-\EOF &&
+	echo Hook ran $1 >>actual
+	EOF
+
+	cat >expect <<-\EOF &&
+	Test hook
+	Hook ran one
+	Hook ran two
+	Hook ran three
+	Hook ran four
+	EOF
+
+	# Test various ways of specifying the path. See also
+	# t1350-config-hooks-path.sh
+	>actual &&
+	git hook run test-hook -- ignored 2>>actual &&
+	git -c core.hooksPath=my-hooks hook run test-hook -- one 2>>actual &&
+	git -c core.hooksPath=my-hooks/ hook run test-hook -- two 2>>actual &&
+	git -c core.hooksPath="$PWD/my-hooks" hook run test-hook -- three 2>>actual &&
+	git -c core.hooksPath="$PWD/my-hooks/" hook run test-hook -- four 2>>actual &&
+	test_cmp expect actual
+'
+
+test_done
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v2 02/13] gc: use hook library for pre-auto-gc hook
  2021-10-15  9:43 [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
  2021-10-15  9:43 ` [PATCH v2 01/13] hook: add 'run' subcommand Ævar Arnfjörð Bjarmason
@ 2021-10-15  9:43 ` Ævar Arnfjörð Bjarmason
  2021-10-15 16:24   ` Emily Shaffer
  2021-10-15  9:43 ` [PATCH v2 03/13] rebase: convert pre-rebase to use hook.h Ævar Arnfjörð Bjarmason
                   ` (13 subsequent siblings)
  15 siblings, 1 reply; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-15  9:43 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the pre-auto-gc hook away from run-command.h to and over to the
new hook.h library.

To do this introduce a simple run_hooks_oneshot() wrapper, we'll be
using it extensively for these simple cases of wanting to run a single
hook under a given name, and having it free the memory we allocate for
us.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/gc.c |  3 ++-
 hook.c       | 20 ++++++++++++++++++++
 hook.h       | 13 +++++++++++++
 3 files changed, 35 insertions(+), 1 deletion(-)

diff --git a/builtin/gc.c b/builtin/gc.c
index 6b3de3dd514..95939e77f53 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -32,6 +32,7 @@
 #include "remote.h"
 #include "object-store.h"
 #include "exec-cmd.h"
+#include "hook.h"
 
 #define FAILED_RUN "failed to run %s"
 
@@ -394,7 +395,7 @@ static int need_to_gc(void)
 	else
 		return 0;
 
-	if (run_hook_le(NULL, "pre-auto-gc", NULL))
+	if (run_hooks_oneshot("pre-auto-gc", NULL))
 		return 0;
 	return 1;
 }
diff --git a/hook.c b/hook.c
index 20a1a4fec33..9951015dc66 100644
--- a/hook.c
+++ b/hook.c
@@ -131,3 +131,23 @@ int run_hooks(const char *hook_name, const char *hook_path,
 
 	return cb_data.rc;
 }
+
+int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options)
+{
+	const char *hook_path;
+	struct run_hooks_opt hook_opt_scratch = RUN_HOOKS_OPT_INIT;
+	int ret = 0;
+
+	if (!options)
+		options = &hook_opt_scratch;
+
+	hook_path = find_hook(hook_name);
+	if (!hook_path)
+		goto cleanup;
+
+	ret = run_hooks(hook_name, hook_path, options);
+cleanup:
+	run_hooks_opt_clear(options);
+
+	return ret;
+}
diff --git a/hook.h b/hook.h
index a9317c3f95e..9e4d10df63e 100644
--- a/hook.h
+++ b/hook.h
@@ -44,7 +44,20 @@ void run_hooks_opt_clear(struct run_hooks_opt *o);
 /**
  * Takes an already resolved hook found via find_hook() and runs
  * it. Does not call run_hooks_opt_clear() for you.
+ *
+ * See run_hooks_oneshot() for the simpler one-shot API.
  */
 int run_hooks(const char *hookname, const char *hook_path,
 	      struct run_hooks_opt *options);
+
+/**
+ * Calls find_hook() on your "hook_name" and runs the hooks (if any)
+ * with run_hooks().
+ *
+ * If "options" is provided calls run_hooks_opt_clear() on it for
+ * you. If "options" is NULL the default options from
+ * RUN_HOOKS_OPT_INIT will be used.
+ */
+int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options);
+
 #endif
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v2 03/13] rebase: convert pre-rebase to use hook.h
  2021-10-15  9:43 [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
  2021-10-15  9:43 ` [PATCH v2 01/13] hook: add 'run' subcommand Ævar Arnfjörð Bjarmason
  2021-10-15  9:43 ` [PATCH v2 02/13] gc: use hook library for pre-auto-gc hook Ævar Arnfjörð Bjarmason
@ 2021-10-15  9:43 ` Ævar Arnfjörð Bjarmason
  2021-10-15 16:25   ` Emily Shaffer
  2021-10-15  9:43 ` [PATCH v2 04/13] am: convert applypatch " Ævar Arnfjörð Bjarmason
                   ` (12 subsequent siblings)
  15 siblings, 1 reply; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-15  9:43 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the pre-rebase hook away from run-command.h to and over to the
new hook.h library.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/rebase.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 8c6393f6d78..9a44939d662 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -28,6 +28,7 @@
 #include "sequencer.h"
 #include "rebase-interactive.h"
 #include "reset.h"
+#include "hook.h"
 
 #define DEFAULT_REFLOG_ACTION "rebase"
 
@@ -1305,6 +1306,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	char *squash_onto_name = NULL;
 	int reschedule_failed_exec = -1;
 	int allow_preemptive_ff = 1;
+	struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;
 	struct option builtin_rebase_options[] = {
 		OPT_STRING(0, "onto", &options.onto_name,
 			   N_("revision"),
@@ -2021,9 +2023,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	}
 
 	/* If a hook exists, give it a chance to interrupt*/
+	strvec_push(&hook_opt.args, options.upstream_arg);
+	if (argc)
+		strvec_push(&hook_opt.args, argv[0]);
 	if (!ok_to_skip_pre_rebase &&
-	    run_hook_le(NULL, "pre-rebase", options.upstream_arg,
-			argc ? argv[0] : NULL, NULL))
+	    run_hooks_oneshot("pre-rebase", &hook_opt))
 		die(_("The pre-rebase hook refused to rebase."));
 
 	if (options.flags & REBASE_DIFFSTAT) {
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v2 04/13] am: convert applypatch to use hook.h
  2021-10-15  9:43 [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                   ` (2 preceding siblings ...)
  2021-10-15  9:43 ` [PATCH v2 03/13] rebase: convert pre-rebase to use hook.h Ævar Arnfjörð Bjarmason
@ 2021-10-15  9:43 ` Ævar Arnfjörð Bjarmason
  2021-10-15 16:25   ` Emily Shaffer
  2021-10-15  9:43 ` [PATCH v2 05/13] hooks: convert 'post-checkout' hook to hook library Ævar Arnfjörð Bjarmason
                   ` (11 subsequent siblings)
  15 siblings, 1 reply; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-15  9:43 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Teach pre-applypatch, post-applypatch, and applypatch-msg to use the
hook.h library instead of the run-command.h library.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/am.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..5528f131a81 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -446,9 +446,11 @@ static void am_destroy(const struct am_state *state)
 static int run_applypatch_msg_hook(struct am_state *state)
 {
 	int ret;
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
 
 	assert(state->msg);
-	ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL);
+	strvec_push(&opt.args, am_path(state, "final-commit"));
+	ret = run_hooks_oneshot("applypatch-msg", &opt);
 
 	if (!ret) {
 		FREE_AND_NULL(state->msg);
@@ -1609,7 +1611,7 @@ static void do_commit(const struct am_state *state)
 	const char *reflog_msg, *author, *committer = NULL;
 	struct strbuf sb = STRBUF_INIT;
 
-	if (run_hook_le(NULL, "pre-applypatch", NULL))
+	if (run_hooks_oneshot("pre-applypatch", NULL))
 		exit(1);
 
 	if (write_cache_as_tree(&tree, 0, NULL))
@@ -1661,7 +1663,7 @@ static void do_commit(const struct am_state *state)
 		fclose(fp);
 	}
 
-	run_hook_le(NULL, "post-applypatch", NULL);
+	run_hooks_oneshot("post-applypatch", NULL);
 
 	strbuf_release(&sb);
 }
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v2 05/13] hooks: convert 'post-checkout' hook to hook library
  2021-10-15  9:43 [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                   ` (3 preceding siblings ...)
  2021-10-15  9:43 ` [PATCH v2 04/13] am: convert applypatch " Ævar Arnfjörð Bjarmason
@ 2021-10-15  9:43 ` Ævar Arnfjörð Bjarmason
  2021-10-15 16:25   ` Emily Shaffer
  2021-10-15  9:43 ` [PATCH v2 06/13] merge: convert post-merge to use hook.h Ævar Arnfjörð Bjarmason
                   ` (10 subsequent siblings)
  15 siblings, 1 reply; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-15  9:43 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the running of the 'post-checkout' hook away from run-command.h
to the new hook.h library. For "worktree" this requires a change to it
to run the hooks from a given directory.

We could strictly speaking skip the "absolute_path" flag and just
check if "dir" is specified, but let's split them up for clarity, as
well as for any future user who'd like to set "dir" but not implicitly
change the argument to an absolute path.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/checkout.c | 14 +++++++++-----
 builtin/clone.c    |  7 +++++--
 builtin/worktree.c | 28 ++++++++++++----------------
 hook.c             | 10 ++++++++++
 hook.h             |  9 +++++++++
 read-cache.c       |  1 +
 reset.c            | 14 ++++++++++----
 7 files changed, 56 insertions(+), 27 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index cbf73b8c9f6..54cbba821a8 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -9,6 +9,7 @@
 #include "config.h"
 #include "diff.h"
 #include "dir.h"
+#include "hook.h"
 #include "ll-merge.h"
 #include "lockfile.h"
 #include "merge-recursive.h"
@@ -106,13 +107,16 @@ struct branch_info {
 static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,
 			      int changed)
 {
-	return run_hook_le(NULL, "post-checkout",
-			   oid_to_hex(old_commit ? &old_commit->object.oid : null_oid()),
-			   oid_to_hex(new_commit ? &new_commit->object.oid : null_oid()),
-			   changed ? "1" : "0", NULL);
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+
 	/* "new_commit" can be NULL when checking out from the index before
 	   a commit exists. */
-
+	strvec_pushl(&opt.args,
+		     oid_to_hex(old_commit ? &old_commit->object.oid : null_oid()),
+		     oid_to_hex(new_commit ? &new_commit->object.oid : null_oid()),
+		     changed ? "1" : "0",
+		     NULL);
+	return run_hooks_oneshot("post-checkout", &opt);
 }
 
 static int update_some(const struct object_id *oid, struct strbuf *base,
diff --git a/builtin/clone.c b/builtin/clone.c
index 559acf9e036..53ca5ca0eb2 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -32,6 +32,7 @@
 #include "connected.h"
 #include "packfile.h"
 #include "list-objects-filter-options.h"
+#include "hook.h"
 
 /*
  * Overall FIXMEs:
@@ -659,6 +660,7 @@ static int checkout(int submodule_progress)
 	struct tree *tree;
 	struct tree_desc t;
 	int err = 0;
+	struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;
 
 	if (option_no_checkout)
 		return 0;
@@ -705,8 +707,9 @@ static int checkout(int submodule_progress)
 	if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
 		die(_("unable to write new index file"));
 
-	err |= run_hook_le(NULL, "post-checkout", oid_to_hex(null_oid()),
-			   oid_to_hex(&oid), "1", NULL);
+	strvec_pushl(&hook_opt.args, oid_to_hex(null_oid()), oid_to_hex(&oid),
+		     "1", NULL);
+	err |= run_hooks_oneshot("post-checkout", &hook_opt);
 
 	if (!err && (option_recurse_submodules.nr > 0)) {
 		struct strvec args = STRVEC_INIT;
diff --git a/builtin/worktree.c b/builtin/worktree.c
index d22ece93e1a..330867c19bf 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -382,22 +382,18 @@ static int add_worktree(const char *path, const char *refname,
 	 * is_junk is cleared, but do return appropriate code when hook fails.
 	 */
 	if (!ret && opts->checkout) {
-		const char *hook = find_hook("post-checkout");
-		if (hook) {
-			const char *env[] = { "GIT_DIR", "GIT_WORK_TREE", NULL };
-			cp.git_cmd = 0;
-			cp.no_stdin = 1;
-			cp.stdout_to_stderr = 1;
-			cp.dir = path;
-			cp.env = env;
-			cp.argv = NULL;
-			cp.trace2_hook_name = "post-checkout";
-			strvec_pushl(&cp.args, absolute_path(hook),
-				     oid_to_hex(null_oid()),
-				     oid_to_hex(&commit->object.oid),
-				     "1", NULL);
-			ret = run_command(&cp);
-		}
+		struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+
+		strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL);
+		strvec_pushl(&opt.args,
+			     oid_to_hex(null_oid()),
+			     oid_to_hex(&commit->object.oid),
+			     "1",
+			     NULL);
+		opt.dir = path;
+		opt.absolute_path = 1;
+
+		ret = run_hooks_oneshot("post-checkout", &opt);
 	}
 
 	strvec_clear(&child_env);
diff --git a/hook.c b/hook.c
index 9951015dc66..756f549e5b2 100644
--- a/hook.c
+++ b/hook.c
@@ -63,6 +63,7 @@ static int pick_next_hook(struct child_process *cp,
 	cp->env = hook_cb->options->env.v;
 	cp->stdout_to_stderr = 1;
 	cp->trace2_hook_name = hook_cb->hook_name;
+	cp->dir = hook_cb->options->dir;
 
 	strvec_push(&cp->args, hook_path);
 	strvec_pushv(&cp->args, hook_cb->options->args.v);
@@ -110,6 +111,7 @@ static int notify_hook_finished(int result,
 int run_hooks(const char *hook_name, const char *hook_path,
 	      struct run_hooks_opt *options)
 {
+	struct strbuf abs_path = STRBUF_INIT;
 	struct hook_cb_data cb_data = {
 		.rc = 0,
 		.hook_name = hook_name,
@@ -121,6 +123,11 @@ int run_hooks(const char *hook_name, const char *hook_path,
 	if (!options)
 		BUG("a struct run_hooks_opt must be provided to run_hooks");
 
+	if (options->absolute_path) {
+		strbuf_add_absolute_path(&abs_path, hook_path);
+		hook_path = abs_path.buf;
+	}
+
 	run_processes_parallel_tr2(jobs,
 				   pick_next_hook,
 				   notify_start_failure,
@@ -129,6 +136,9 @@ int run_hooks(const char *hook_name, const char *hook_path,
 				   "hook",
 				   hook_name);
 
+	if (options->absolute_path)
+		strbuf_release(&abs_path);
+
 	return cb_data.rc;
 }
 
diff --git a/hook.h b/hook.h
index 9e4d10df63e..252a605b125 100644
--- a/hook.h
+++ b/hook.h
@@ -9,6 +9,15 @@ struct run_hooks_opt
 
 	/* Args to be passed to each hook */
 	struct strvec args;
+
+	/*
+	 * Resolve and run the "absolute_path(hook)" instead of
+	 * "hook". Used for "git worktree" hooks
+	 */
+	int absolute_path;
+
+	/* Path to initial working directory for subprocess */
+	const char *dir;
 };
 
 #define RUN_HOOKS_OPT_INIT { \
diff --git a/read-cache.c b/read-cache.c
index a78b88a41bf..9773118d078 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -28,6 +28,7 @@
 #include "sparse-index.h"
 #include "csum-file.h"
 #include "promisor-remote.h"
+#include "hook.h"
 
 /* Mask for the name length in ce_flags in the on-disk index */
 
diff --git a/reset.c b/reset.c
index f214df3d96c..4234dd345db 100644
--- a/reset.c
+++ b/reset.c
@@ -7,6 +7,7 @@
 #include "tree-walk.h"
 #include "tree.h"
 #include "unpack-trees.h"
+#include "hook.h"
 
 int reset_head(struct repository *r, struct object_id *oid, const char *action,
 	       const char *switch_to_branch, unsigned flags,
@@ -126,10 +127,15 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,
 			ret = create_symref("HEAD", switch_to_branch,
 					    reflog_head);
 	}
-	if (run_hook)
-		run_hook_le(NULL, "post-checkout",
-			    oid_to_hex(orig ? orig : null_oid()),
-			    oid_to_hex(oid), "1", NULL);
+	if (run_hook) {
+		struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+		strvec_pushl(&opt.args,
+			     oid_to_hex(orig ? orig : null_oid()),
+			     oid_to_hex(oid),
+			     "1",
+			     NULL);
+		run_hooks_oneshot("post-checkout", &opt);
+	}
 
 leave_reset_head:
 	strbuf_release(&msg);
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v2 06/13] merge: convert post-merge to use hook.h
  2021-10-15  9:43 [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                   ` (4 preceding siblings ...)
  2021-10-15  9:43 ` [PATCH v2 05/13] hooks: convert 'post-checkout' hook to hook library Ævar Arnfjörð Bjarmason
@ 2021-10-15  9:43 ` Ævar Arnfjörð Bjarmason
  2021-10-15 16:26   ` Emily Shaffer
  2021-10-15  9:43 ` [PATCH v2 07/13] git hook run: add an --ignore-missing flag Ævar Arnfjörð Bjarmason
                   ` (9 subsequent siblings)
  15 siblings, 1 reply; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-15  9:43 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Teach post-merge to use the hook.h library instead of the
run-command.h library to run hooks.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/merge.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/builtin/merge.c b/builtin/merge.c
index cc4a910c69b..c48b875b82b 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -448,6 +448,7 @@ static void finish(struct commit *head_commit,
 		   const struct object_id *new_head, const char *msg)
 {
 	struct strbuf reflog_message = STRBUF_INIT;
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
 	const struct object_id *head = &head_commit->object.oid;
 
 	if (!msg)
@@ -488,7 +489,8 @@ static void finish(struct commit *head_commit,
 	}
 
 	/* Run a post-merge hook */
-	run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL);
+	strvec_push(&opt.args, squash ? "1" : "0");
+	run_hooks_oneshot("post-merge", &opt);
 
 	apply_autostash(git_path_merge_autostash(the_repository));
 	strbuf_release(&reflog_message);
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v2 07/13] git hook run: add an --ignore-missing flag
  2021-10-15  9:43 [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                   ` (5 preceding siblings ...)
  2021-10-15  9:43 ` [PATCH v2 06/13] merge: convert post-merge to use hook.h Ævar Arnfjörð Bjarmason
@ 2021-10-15  9:43 ` Ævar Arnfjörð Bjarmason
  2021-10-15 16:26   ` Emily Shaffer
  2021-10-15  9:43 ` [PATCH v2 08/13] send-email: use 'git hook run' for 'sendemail-validate' Ævar Arnfjörð Bjarmason
                   ` (8 subsequent siblings)
  15 siblings, 1 reply; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-15  9:43 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

For certain one-shot hooks we'd like to optimistically run them, and
not complain if they don't exist. This will be used by send-email in a
subsequent commit.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 Documentation/git-hook.txt | 10 +++++++++-
 builtin/hook.c             |  7 ++++++-
 t/t1800-hook.sh            |  5 +++++
 3 files changed, 20 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt
index e39b1b5d069..77c3a8ad909 100644
--- a/Documentation/git-hook.txt
+++ b/Documentation/git-hook.txt
@@ -8,7 +8,7 @@ git-hook - Run git hooks
 SYNOPSIS
 --------
 [verse]
-'git hook' run <hook-name> [-- <hook-args>]
+'git hook' run [--ignore-missing] <hook-name> [-- <hook-args>]
 
 DESCRIPTION
 -----------
@@ -28,6 +28,14 @@ Any positional arguments to the hook should be passed after a
 mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See
 linkgit:githooks[5] for arguments hooks might expect (if any).
 
+OPTIONS
+-------
+
+--ignore-missing::
+	Ignore any missing hook by quietly returning zero. Used for
+	tools that want to do a blind one-shot run of a hook that may
+	or may not be present.
+
 SEE ALSO
 --------
 linkgit:githooks[5]
diff --git a/builtin/hook.c b/builtin/hook.c
index 41dd15550cf..fa26454990d 100644
--- a/builtin/hook.c
+++ b/builtin/hook.c
@@ -7,7 +7,7 @@
 #include "strvec.h"
 
 #define BUILTIN_HOOK_RUN_USAGE \
-	N_("git hook run <hook-name> [-- <hook-args>]")
+	N_("git hook run [--ignore-missing] <hook-name> [-- <hook-args>]")
 
 static const char * const builtin_hook_usage[] = {
 	BUILTIN_HOOK_RUN_USAGE,
@@ -23,9 +23,12 @@ static int run(int argc, const char **argv, const char *prefix)
 {
 	int i;
 	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+	int ignore_missing = 0;
 	const char *hook_name;
 	const char *hook_path;
 	struct option run_options[] = {
+		OPT_BOOL(0, "ignore-missing", &ignore_missing,
+			 N_("silently ignore missing requested <hook-name>")),
 		OPT_END(),
 	};
 	int ret;
@@ -53,6 +56,8 @@ static int run(int argc, const char **argv, const char *prefix)
 	git_config(git_default_config, NULL);
 
 	hook_name = argv[0];
+	if (ignore_missing)
+		return run_hooks_oneshot(hook_name, &opt);
 	hook_path = find_hook(hook_name);
 	if (!hook_path) {
 		error("cannot find a hook named %s", hook_name);
diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh
index 3aea1b105f0..29718aa9913 100755
--- a/t/t1800-hook.sh
+++ b/t/t1800-hook.sh
@@ -21,6 +21,11 @@ test_expect_success 'git hook run: nonexistent hook' '
 	test_cmp stderr.expect stderr.actual
 '
 
+test_expect_success 'git hook run: nonexistent hook with --ignore-missing' '
+	git hook run --ignore-missing does-not-exist 2>stderr.actual &&
+	test_must_be_empty stderr.actual
+'
+
 test_expect_success 'git hook run: basic' '
 	write_script .git/hooks/test-hook <<-EOF &&
 	echo Test hook
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v2 08/13] send-email: use 'git hook run' for 'sendemail-validate'
  2021-10-15  9:43 [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                   ` (6 preceding siblings ...)
  2021-10-15  9:43 ` [PATCH v2 07/13] git hook run: add an --ignore-missing flag Ævar Arnfjörð Bjarmason
@ 2021-10-15  9:43 ` Ævar Arnfjörð Bjarmason
  2021-10-15 17:07   ` Emily Shaffer
  2021-10-15  9:43 ` [PATCH v2 09/13] git-p4: use 'git hook' to run hooks Ævar Arnfjörð Bjarmason
                   ` (7 subsequent siblings)
  15 siblings, 1 reply; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-15  9:43 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Change the "sendmail-validate" hook to be run via the "git hook run"
wrapper instead of via a direct invocation.

This is the smallest possibly change to get "send-email" using "git
hook run". We still check the hook itself with "-x", and set a
"GIT_DIR" variable, both of which are asserted by our tests. We'll
need to get rid of this special behavior if we start running N hooks,
but for now let's be as close to bug-for-bug compatible as possible.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 git-send-email.perl   | 22 ++++++++++++++--------
 t/t9001-send-email.sh |  4 ++--
 2 files changed, 16 insertions(+), 10 deletions(-)

diff --git a/git-send-email.perl b/git-send-email.perl
index 5262d88ee32..4c20c0bbb79 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -195,13 +195,13 @@ sub format_2822_time {
 my $editor;
 
 sub system_or_msg {
-	my ($args, $msg) = @_;
+	my ($args, $msg, $cmd_name) = @_;
 	system(@$args);
 	my $signalled = $? & 127;
 	my $exit_code = $? >> 8;
 	return unless $signalled or $exit_code;
 
-	my @sprintf_args = ($args->[0], $exit_code);
+	my @sprintf_args = ($cmd_name ? $cmd_name : $args->[0], $exit_code);
 	if (defined $msg) {
 		# Quiet the 'redundant' warning category, except we
 		# need to support down to Perl 5.8, so we can't do a
@@ -2039,10 +2039,10 @@ sub validate_patch {
 	my ($fn, $xfer_encoding) = @_;
 
 	if ($repo) {
+		my $hook_name = 'sendemail-validate';
 		my $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks');
 		require File::Spec;
-		my $validate_hook = File::Spec->catfile($hooks_path,
-					    'sendemail-validate');
+		my $validate_hook = File::Spec->catfile($hooks_path, $hook_name);
 		my $hook_error;
 		if (-x $validate_hook) {
 			require Cwd;
@@ -2052,13 +2052,19 @@ sub validate_patch {
 			chdir($repo->wc_path() or $repo->repo_path())
 				or die("chdir: $!");
 			local $ENV{"GIT_DIR"} = $repo->repo_path();
-			$hook_error = system_or_msg([$validate_hook, $target]);
+			my @cmd = ("git", "hook", "run", "--ignore-missing",
+				    $hook_name, "--");
+			my @cmd_msg = (@cmd, "<patch>");
+			my @cmd_run = (@cmd, $target);
+			$hook_error = system_or_msg(\@cmd_run, undef, "@cmd_msg");
 			chdir($cwd_save) or die("chdir: $!");
 		}
 		if ($hook_error) {
-			die sprintf(__("fatal: %s: rejected by sendemail-validate hook\n" .
-				       "%s\n" .
-				       "warning: no patches were sent\n"), $fn, $hook_error);
+			$hook_error = sprintf(__("fatal: %s: rejected by %s hook\n" .
+						 $hook_error . "\n" .
+						 "warning: no patches were sent\n"),
+					      $fn, $hook_name);
+			die $hook_error;
 		}
 	}
 
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index aa0c20499ba..84d0f40d76a 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -539,7 +539,7 @@ test_expect_success $PREREQ "--validate respects relative core.hooksPath path" '
 	test_path_is_file my-hooks.ran &&
 	cat >expect <<-EOF &&
 	fatal: longline.patch: rejected by sendemail-validate hook
-	fatal: command '"'"'my-hooks/sendemail-validate'"'"' died with exit code 1
+	fatal: command '"'"'git hook run --ignore-missing sendemail-validate -- <patch>'"'"' died with exit code 1
 	warning: no patches were sent
 	EOF
 	test_cmp expect actual
@@ -558,7 +558,7 @@ test_expect_success $PREREQ "--validate respects absolute core.hooksPath path" '
 	test_path_is_file my-hooks.ran &&
 	cat >expect <<-EOF &&
 	fatal: longline.patch: rejected by sendemail-validate hook
-	fatal: command '"'"'$hooks_path/sendemail-validate'"'"' died with exit code 1
+	fatal: command '"'"'git hook run --ignore-missing sendemail-validate -- <patch>'"'"' died with exit code 1
 	warning: no patches were sent
 	EOF
 	test_cmp expect actual
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v2 09/13] git-p4: use 'git hook' to run hooks
  2021-10-15  9:43 [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                   ` (7 preceding siblings ...)
  2021-10-15  9:43 ` [PATCH v2 08/13] send-email: use 'git hook run' for 'sendemail-validate' Ævar Arnfjörð Bjarmason
@ 2021-10-15  9:43 ` Ævar Arnfjörð Bjarmason
  2021-10-15 17:08   ` Emily Shaffer
  2021-10-15  9:43 ` [PATCH v2 10/13] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h Ævar Arnfjörð Bjarmason
                   ` (6 subsequent siblings)
  15 siblings, 1 reply; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-15  9:43 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Instead of duplicating the behavior of run-command.h:run_hook_le() in
Python, we can directly call 'git hook run'. We emulate the existence
check with the --ignore-missing flag.

As this is the last hook execution in git.git to not go through "git
hook run" or the hook.[ch] library we can now be absolutely sure that
our assertion in hook.c that only hooks known by the generated (from
githooks(5)) hook-list.h are permitted.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 git-p4.py | 72 ++++++-------------------------------------------------
 1 file changed, 7 insertions(+), 65 deletions(-)

diff --git a/git-p4.py b/git-p4.py
index 2b4500226aa..1f24cbf0bca 100755
--- a/git-p4.py
+++ b/git-p4.py
@@ -207,71 +207,13 @@ def decode_path(path):
         return path
 
 def run_git_hook(cmd, param=[]):
-    """Execute a hook if the hook exists."""
-    if verbose:
-        sys.stderr.write("Looking for hook: %s\n" % cmd)
-        sys.stderr.flush()
-
-    hooks_path = gitConfig("core.hooksPath")
-    if len(hooks_path) <= 0:
-        hooks_path = os.path.join(os.environ["GIT_DIR"], "hooks")
-
-    if not isinstance(param, list):
-        param=[param]
-
-    # resolve hook file name, OS depdenent
-    hook_file = os.path.join(hooks_path, cmd)
-    if platform.system() == 'Windows':
-        if not os.path.isfile(hook_file):
-            # look for the file with an extension
-            files = glob.glob(hook_file + ".*")
-            if not files:
-                return True
-            files.sort()
-            hook_file = files.pop()
-            while hook_file.upper().endswith(".SAMPLE"):
-                # The file is a sample hook. We don't want it
-                if len(files) > 0:
-                    hook_file = files.pop()
-                else:
-                    return True
-
-    if not os.path.isfile(hook_file) or not os.access(hook_file, os.X_OK):
-        return True
-
-    return run_hook_command(hook_file, param) == 0
-
-def run_hook_command(cmd, param):
-    """Executes a git hook command
-       cmd = the command line file to be executed. This can be
-       a file that is run by OS association.
-
-       param = a list of parameters to pass to the cmd command
-
-       On windows, the extension is checked to see if it should
-       be run with the Git for Windows Bash shell.  If there
-       is no file extension, the file is deemed a bash shell
-       and will be handed off to sh.exe. Otherwise, Windows
-       will be called with the shell to handle the file assocation.
-
-       For non Windows operating systems, the file is called
-       as an executable.
-    """
-    cli = [cmd] + param
-    use_shell = False
-    if platform.system() == 'Windows':
-        (root,ext) = os.path.splitext(cmd)
-        if ext == "":
-            exe_path = os.environ.get("EXEPATH")
-            if exe_path is None:
-                exe_path = ""
-            else:
-                exe_path = os.path.join(exe_path, "bin")
-            cli = [os.path.join(exe_path, "SH.EXE")] + cli
-        else:
-            use_shell = True
-    return subprocess.call(cli, shell=use_shell)
-
+    """args are specified with -a <arg> -a <arg> -a <arg>"""
+    args = ['git', 'hook', 'run', '--ignore-missing', cmd]
+    if param:
+        args.append("--")
+        for p in param:
+            args.append(p)
+    return subprocess.call(args) == 0
 
 def write_pipe(c, stdin):
     if verbose:
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v2 10/13] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h
  2021-10-15  9:43 [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                   ` (8 preceding siblings ...)
  2021-10-15  9:43 ` [PATCH v2 09/13] git-p4: use 'git hook' to run hooks Ævar Arnfjörð Bjarmason
@ 2021-10-15  9:43 ` Ævar Arnfjörð Bjarmason
  2021-10-15 17:15   ` Emily Shaffer
  2021-10-15  9:43 ` [PATCH v2 11/13] read-cache: convert post-index-change to use hook.h Ævar Arnfjörð Bjarmason
                   ` (5 subsequent siblings)
  15 siblings, 1 reply; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-15  9:43 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move these hooks hook away from run-command.h to and over to the new
hook.h library.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 commit.c | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/commit.c b/commit.c
index 551de4903c1..4e7cbd7585d 100644
--- a/commit.c
+++ b/commit.c
@@ -21,6 +21,7 @@
 #include "commit-reach.h"
 #include "run-command.h"
 #include "shallow.h"
+#include "hook.h"
 
 static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
 
@@ -1700,22 +1701,22 @@ size_t ignore_non_trailer(const char *buf, size_t len)
 int run_commit_hook(int editor_is_used, const char *index_file,
 		    const char *name, ...)
 {
-	struct strvec hook_env = STRVEC_INIT;
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
 	va_list args;
-	int ret;
+	const char *arg;
 
-	strvec_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file);
+	strvec_pushf(&opt.env, "GIT_INDEX_FILE=%s", index_file);
 
 	/*
 	 * Let the hook know that no editor will be launched.
 	 */
 	if (!editor_is_used)
-		strvec_push(&hook_env, "GIT_EDITOR=:");
+		strvec_push(&opt.env, "GIT_EDITOR=:");
 
 	va_start(args, name);
-	ret = run_hook_ve(hook_env.v, name, args);
+	while ((arg = va_arg(args, const char *)))
+		strvec_push(&opt.args, arg);
 	va_end(args);
-	strvec_clear(&hook_env);
 
-	return ret;
+	return run_hooks_oneshot(name, &opt);
 }
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v2 11/13] read-cache: convert post-index-change to use hook.h
  2021-10-15  9:43 [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                   ` (9 preceding siblings ...)
  2021-10-15  9:43 ` [PATCH v2 10/13] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h Ævar Arnfjörð Bjarmason
@ 2021-10-15  9:43 ` Ævar Arnfjörð Bjarmason
  2021-10-15 17:17   ` Emily Shaffer
  2021-10-15  9:43 ` [PATCH v2 12/13] receive-pack: convert push-to-checkout hook to hook.h Ævar Arnfjörð Bjarmason
                   ` (4 subsequent siblings)
  15 siblings, 1 reply; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-15  9:43 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the post-index-change hook away from run-command.h to and over to
the new hook.h library.

This removes the last direct user of "run_hook_ve()" outside of
run-command.c ("run_hook_le()" still uses it). So we can make the
function static now. A subsequent commit will remove this code
entirely when "run_hook_le()" itself goes away.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 read-cache.c  | 10 +++++++---
 run-command.c |  2 +-
 run-command.h |  1 -
 3 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/read-cache.c b/read-cache.c
index 9773118d078..84cc3d88196 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -3087,6 +3087,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
 {
 	int ret;
 	int was_full = !istate->sparse_index;
+	struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;
 
 	ret = convert_to_sparse(istate, 0);
 
@@ -3115,9 +3116,12 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
 	else
 		ret = close_lock_file_gently(lock);
 
-	run_hook_le(NULL, "post-index-change",
-			istate->updated_workdir ? "1" : "0",
-			istate->updated_skipworktree ? "1" : "0", NULL);
+	strvec_pushl(&hook_opt.args,
+		     istate->updated_workdir ? "1" : "0",
+		     istate->updated_skipworktree ? "1" : "0",
+		     NULL);
+	run_hooks_oneshot("post-index-change", &hook_opt);
+
 	istate->updated_workdir = 0;
 	istate->updated_skipworktree = 0;
 
diff --git a/run-command.c b/run-command.c
index 7ef5cc712a9..d92e670c8ed 100644
--- a/run-command.c
+++ b/run-command.c
@@ -1323,7 +1323,7 @@ int async_with_fork(void)
 #endif
 }
 
-int run_hook_ve(const char *const *env, const char *name, va_list args)
+static int run_hook_ve(const char *const *env, const char *name, va_list args)
 {
 	struct child_process hook = CHILD_PROCESS_INIT;
 	const char *p;
diff --git a/run-command.h b/run-command.h
index 49878262584..3fa7454cf8a 100644
--- a/run-command.h
+++ b/run-command.h
@@ -239,7 +239,6 @@ int run_command(struct child_process *);
  */
 LAST_ARG_MUST_BE_NULL
 int run_hook_le(const char *const *env, const char *name, ...);
-int run_hook_ve(const char *const *env, const char *name, va_list args);
 
 /*
  * Trigger an auto-gc
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v2 12/13] receive-pack: convert push-to-checkout hook to hook.h
  2021-10-15  9:43 [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                   ` (10 preceding siblings ...)
  2021-10-15  9:43 ` [PATCH v2 11/13] read-cache: convert post-index-change to use hook.h Ævar Arnfjörð Bjarmason
@ 2021-10-15  9:43 ` Ævar Arnfjörð Bjarmason
  2021-10-15  9:43 ` [PATCH v2 13/13] run-command: remove old run_hook_{le,ve}() hook API Ævar Arnfjörð Bjarmason
                   ` (3 subsequent siblings)
  15 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-15  9:43 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the push-to-checkout hook away from run-command.h to and over to
the new hook.h library.

This removes the last direct user of run_hook_le(), so we could remove
that function now, but let's leave that to a follow-up cleanup commit.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/receive-pack.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 25cc0c907e1..22e7a431728 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1435,9 +1435,12 @@ static const char *push_to_checkout(unsigned char *hash,
 				    struct strvec *env,
 				    const char *work_tree)
 {
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+
 	strvec_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree));
-	if (run_hook_le(env->v, push_to_checkout_hook,
-			hash_to_hex(hash), NULL))
+	strvec_pushv(&opt.env, env->v);
+	strvec_push(&opt.args, hash_to_hex(hash));
+	if (run_hooks_oneshot(push_to_checkout_hook, &opt))
 		return "push-to-checkout hook declined";
 	else
 		return NULL;
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v2 13/13] run-command: remove old run_hook_{le,ve}() hook API
  2021-10-15  9:43 [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                   ` (11 preceding siblings ...)
  2021-10-15  9:43 ` [PATCH v2 12/13] receive-pack: convert push-to-checkout hook to hook.h Ævar Arnfjörð Bjarmason
@ 2021-10-15  9:43 ` Ævar Arnfjörð Bjarmason
  2021-10-15 17:18   ` Emily Shaffer
  2021-10-15 16:23 ` [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion Emily Shaffer
                   ` (2 subsequent siblings)
  15 siblings, 1 reply; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-15  9:43 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

The new hook.h library has replaced all run-command.h hook-related
functionality. So let's delete this dead code.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 run-command.c | 32 --------------------------------
 run-command.h | 16 ----------------
 2 files changed, 48 deletions(-)

diff --git a/run-command.c b/run-command.c
index d92e670c8ed..8a21ff525f3 100644
--- a/run-command.c
+++ b/run-command.c
@@ -1323,38 +1323,6 @@ int async_with_fork(void)
 #endif
 }
 
-static int run_hook_ve(const char *const *env, const char *name, va_list args)
-{
-	struct child_process hook = CHILD_PROCESS_INIT;
-	const char *p;
-
-	p = find_hook(name);
-	if (!p)
-		return 0;
-
-	strvec_push(&hook.args, p);
-	while ((p = va_arg(args, const char *)))
-		strvec_push(&hook.args, p);
-	hook.env = env;
-	hook.no_stdin = 1;
-	hook.stdout_to_stderr = 1;
-	hook.trace2_hook_name = name;
-
-	return run_command(&hook);
-}
-
-int run_hook_le(const char *const *env, const char *name, ...)
-{
-	va_list args;
-	int ret;
-
-	va_start(args, name);
-	ret = run_hook_ve(env, name, args);
-	va_end(args);
-
-	return ret;
-}
-
 struct io_pump {
 	/* initialized by caller */
 	int fd;
diff --git a/run-command.h b/run-command.h
index 3fa7454cf8a..59e1fbff64c 100644
--- a/run-command.h
+++ b/run-command.h
@@ -224,22 +224,6 @@ int finish_command_in_signal(struct child_process *);
  */
 int run_command(struct child_process *);
 
-/**
- * Run a hook.
- * The first argument is a pathname to an index file, or NULL
- * if the hook uses the default index file or no index is needed.
- * The second argument is the name of the hook.
- * The further arguments correspond to the hook arguments.
- * The last argument has to be NULL to terminate the arguments list.
- * If the hook does not exist or is not executable, the return
- * value will be zero.
- * If it is executable, the hook will be executed and the exit
- * status of the hook is returned.
- * On execution, .stdout_to_stderr and .no_stdin will be set.
- */
-LAST_ARG_MUST_BE_NULL
-int run_hook_le(const char *const *env, const char *name, ...);
-
 /*
  * Trigger an auto-gc
  */
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion
  2021-10-15  9:43 [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                   ` (12 preceding siblings ...)
  2021-10-15  9:43 ` [PATCH v2 13/13] run-command: remove old run_hook_{le,ve}() hook API Ævar Arnfjörð Bjarmason
@ 2021-10-15 16:23 ` Emily Shaffer
  2021-10-16  0:58 ` Junio C Hamano
  2021-10-19 23:20 ` [PATCH v3 " Ævar Arnfjörð Bjarmason
  15 siblings, 0 replies; 88+ messages in thread
From: Emily Shaffer @ 2021-10-15 16:23 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Junio C Hamano, Phillip Wood, René Scharfe, Bagas Sanjaya

On Tue, Oct 12, 2021 at 03:30:25PM +0200, Ævar Arnfjörð Bjarmason wrote:
> 
> This series is part 2 of an incremental restart of the greater
> "config-based hooks" topic by Emily Shaffer that I've been helping
> with. See [1] for part 1, and [2] (search for "mark2") for a summary
> of parts to come.
> 
> This goes on top of the "ab/config-based-hooks-1" topic that's already
> in "next" and marked for graduation to "master".
> 
> In this topic we build upon the skeleton hook.[ch] library and build
> infrastructure added in part 1 and add a hook running library and new
> "git hook" command.
> 
> The new "git hook" command is (for now) only for the benefit of
> in-tree non-C programs that need to run hooks, and this series
> converts git-send-email and git-p4 to use it.
> 
> At the end of the series we remove
> "run_hook_{le,ve}()" from run-command.c, as we've migrated all its
> callers.
> 
> What we don't do is convert any of the more complex in-tree hooks that
> require input on stdin such as "pre-receive" and the like, that's in
> later parts once this lands.
> 
> This series is approximately patch 6-20/36 of the previous 36 patch
> ab/config-based-hooks-base topic. A range-diff to that v5[3] is
> included below.
> 
> The changes since that v5 are rather trivial, they are:
> 
>  * Formatting changes to reduce the diff to parts that come after this
>    (which Emily & I were juggling at the time), and re-flowing some
>    overly long lines.
> 
>  * The new test is now marked for SANITIZE=leak testing, with the new
>    test facility I've added recently (and which just landed in
>    "master"). They all pass, the new API doesn't leak.
> 
>  * I rewrote some of the git-send-email.perl code to avoid
>    de-duplication and hardcoding (just using intermediate variables).
> 
> Things to focus on reviewing:
> 
>  * This should all be pretty solid and well tested, but the git-p4.py
>    part in particular I've never tested for real (not having access to
>    p4), and think Emily hasn't either[4].

Correct. I seem to remember wanting to give this a test and not having access
to real perforce to try it against. 

> 
>    The relevant patch looks trivially correct to me[5], and I've tried it
>    out in the Python REPL. But if any of the CC'd people active in
>    git-p4.py development could give it some end-to-end testing that
>    would be much appreciated.
> 
>  * Both Python and Perl code now calls the in-between "git hook run"
>    command rather than calling hooks directly. Will this behave
>    differently due to any special behavior running via a git built-in
>    adds?
> 
>    I vaguely recall a third-party "git-foo" program breaking in the
>    past when invoked as "git foo" but not "git-foo", due to git
>    squatting on SIGINT, but none of that should be relevant here
>    (we're not starting a pager etc.).

I would be very surprised, assuming that we invoke 'git hook' in the same way
that those Python and Perl scripts invoke other 'git whatever' commands...

That said, once we invoke a user hook, all bets are off as far as "we're not
starting a pager or anything". A determined user could find a reason to invoke
a pager, editor, whatever during a hook. I'm curious to know more about this
remembered breakage.

Last time I looked at the range-diff; this time I'll look at the individual
patches, instead.

Note: I looked at a bunch of these offline, so sorry for the sudden
ingress of mails today ;)

 - Emily

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 01/13] hook: add 'run' subcommand
  2021-10-15  9:43 ` [PATCH v2 01/13] hook: add 'run' subcommand Ævar Arnfjörð Bjarmason
@ 2021-10-15 16:24   ` Emily Shaffer
  2021-10-15 17:53     ` Eric Sunshine
  2021-10-19 23:08     ` Ævar Arnfjörð Bjarmason
  0 siblings, 2 replies; 88+ messages in thread
From: Emily Shaffer @ 2021-10-15 16:24 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Junio C Hamano, Phillip Wood, René Scharfe, Bagas Sanjaya

On Tue, Oct 12, 2021 at 03:30:26PM +0200, Ævar Arnfjörð Bjarmason wrote:
> 
> 
> In order to enable hooks to be run as an external process, by a
> standalone Git command, or by tools which wrap Git, provide an external
> means to run all configured hook commands for a given hook event.
> 
> Most of our hooks require more complex functionality than this, but
> let's start with the bare minimum required to support our simplest
> hooks.
> 
> In terms of implementation the usage_with_options() and "goto usage"
> pattern here mirrors that of
> builtin/{commit-graph,multi-pack-index}.c.
> 
> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> ---
> diff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt
> new file mode 100644
> index 00000000000..660d6a992a0
> --- /dev/null
> +++ b/Documentation/git-hook.txt
> @@ -0,0 +1,38 @@
> +git-hook(1)
> +===========
> +
> +NAME
> +----
> +git-hook - run git hooks
> +
> +SYNOPSIS
> +--------
> +[verse]
> +'git hook' run <hook-name> [-- <hook-args>]
> +
> +DESCRIPTION
> +-----------
> +
> +This command is an interface to git hooks (see linkgit:githooks[5]).
> +Currently it only provides a convenience wrapper for running hooks for
> +use by git itself. In the future it might gain other functionality.

s/git/Git/g nit, no? I thought we were somewhat picky about capitalizing Git
when we don't mean `git`?

> +
> +SUBCOMMANDS
> +-----------
> +
> +run::
> +	Run the `<hook-name>` hook. See linkgit:githooks[5] for
> +	the hook names we support.
> ++
> +Any positional arguments to the hook should be passed after an
> +optional `--` (or `--end-of-options`, see linkgit:gitcli[7]). The
> +arguments (if any) differ by hook name, see linkgit:githooks[5] for
> +what those are.
> +
> +SEE ALSO
> +--------
> +linkgit:githooks[5]
> +
> +GIT
> +---
> +Part of the linkgit:git[1] suite

> diff --git a/builtin/hook.c b/builtin/hook.c
> new file mode 100644
> index 00000000000..012a2973b38
> --- /dev/null
> +++ b/builtin/hook.c
> @@ -0,0 +1,90 @@
> +#include "cache.h"
> +#include "builtin.h"
> +#include "config.h"
> +#include "hook.h"
> +#include "parse-options.h"
> +#include "strbuf.h"
> +#include "strvec.h"
> +
> +#define BUILTIN_HOOK_RUN_USAGE \
> +	N_("git hook run <hook-name> [-- <hook-args>]")
> +
> +static const char * const builtin_hook_usage[] = {
> +	BUILTIN_HOOK_RUN_USAGE,
> +	NULL
> +};
> +
> +static const char * const builtin_hook_run_usage[] = {
> +	BUILTIN_HOOK_RUN_USAGE,
> +	NULL
> +};

While some reviewers might think this looks untidy, it does save us some
repetition later on adding other subcommands to 'git hook'. Thanks.

> +
> +static int run(int argc, const char **argv, const char *prefix)
> +{
> +	int i;
> +	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
> +	const char *hook_name;
> +	const char *hook_path;
> +	struct option run_options[] = {
> +		OPT_END(),
> +	};
> +	int ret;
> +
> +	argc = parse_options(argc, argv, prefix, run_options,
> +			     builtin_hook_run_usage,
> +			     PARSE_OPT_KEEP_DASHDASH);

We KEEP_DASHDASH so we can ensure someone did not send us some args they meant
for 'git hook' instead of for the hook script itself. Ok.

> +
> +	if (!argc)
> +		goto usage;
> +
> +	/*
> +	 * Having a -- for "run" when providing <hook-args> is
> +	 * mandatory.
> +	 */
> +	if (argc > 1 && strcmp(argv[1], "--") &&
> +	    strcmp(argv[1], "--end-of-options"))

--end-of-options isn't documented anywhere, right? Fine, I don't mind, but it's
interesting to support it "secretly".

> +		goto usage;
> +
> +	/* Add our arguments, start after -- */
> +	for (i = 2 ; i < argc; i++)
> +		strvec_push(&opt.args, argv[i]);
> +
> +	/* Need to take into account core.hooksPath */
> +	git_config(git_default_config, NULL);

..and git_default_config() sets it up for use in find_hook(). Great.

> +
> +	/*
> +	 * We are not using a plain run_hooks() because we'd like to
> +	 * detect missing hooks. Let's find it ourselves and call
> +	 * run_hooks() instead.
> +	 */

Hmm. In practice, I wonder if this means that callers like 'git-send-email' still
need to check whether there is a 'sendemail-validate' hook before they can call
'git hook run sendemail-validate'? I guess I'll have a look at the work there
and see.

> +	hook_name = argv[0];
> +	hook_path = find_hook(hook_name);
> +	if (!hook_path) {
> +		error("cannot find a hook named %s", hook_name);
> +		return 1;
> +	}
> +
> +	ret = run_hooks(hook_name, hook_path, &opt);
> +	run_hooks_opt_clear(&opt);
> +	return ret;
> +usage:
> +	usage_with_options(builtin_hook_run_usage, run_options);
> +}
> +
> +int cmd_hook(int argc, const char **argv, const char *prefix)
> +{
> +	struct option builtin_hook_options[] = {
> +		OPT_END(),
> +	};
> +
> +	argc = parse_options(argc, argv, NULL, builtin_hook_options,
> +			     builtin_hook_usage, PARSE_OPT_STOP_AT_NON_OPTION);
> +	if (!argc)
> +		goto usage;
> +
> +	if (!strcmp(argv[0], "run"))
> +		return run(argc, argv, prefix);
> +
> +usage:
> +	usage_with_options(builtin_hook_usage, builtin_hook_options);
> +}

The rest of it is very straightforward boilerplate for a builtin. Thanks.


> diff --git a/command-list.txt b/command-list.txt
> index a289f09ed6f..9ccd8e5aebe 100644
> --- a/command-list.txt
> +++ b/command-list.txt
> @@ -103,6 +103,7 @@ git-grep                                mainporcelain           info
>  git-gui                                 mainporcelain
>  git-hash-object                         plumbingmanipulators
>  git-help                                ancillaryinterrogators          complete
> +git-hook                                mainporcelain
>  git-http-backend                        synchingrepositories
>  git-http-fetch                          synchelpers
>  git-http-push                           synchelpers
> diff --git a/git.c b/git.c
> index 60c2784be45..e5891e02021 100644
> --- a/git.c
> +++ b/git.c
> @@ -538,6 +538,7 @@ static struct cmd_struct commands[] = {
>  	{ "grep", cmd_grep, RUN_SETUP_GENTLY },
>  	{ "hash-object", cmd_hash_object },
>  	{ "help", cmd_help },
> +	{ "hook", cmd_hook, RUN_SETUP },

Ok. For now this is still requiring a gitdir. That makes sense to me, because
we are still getting our hooks from $GITDIR/hooks/. Fine.

>  	{ "index-pack", cmd_index_pack, RUN_SETUP_GENTLY | NO_PARSEOPT },
>  	{ "init", cmd_init_db },
>  	{ "init-db", cmd_init_db },
> diff --git a/hook.c b/hook.c
> index 55e1145a4b7..240270db64e 100644
> --- a/hook.c
> +++ b/hook.c
> @@ -1,6 +1,7 @@
>  #include "cache.h"
>  #include "hook.h"
>  #include "run-command.h"
> +#include "config.h"
>  
>  const char *find_hook(const char *name)
>  {
> @@ -40,3 +41,103 @@ int hook_exists(const char *name)
>  {
>  	return !!find_hook(name);
>  }
> +
> +void run_hooks_opt_clear(struct run_hooks_opt *o)
> +{
> +	strvec_clear(&o->env);
> +	strvec_clear(&o->args);
> +}
> +
> +static int pick_next_hook(struct child_process *cp,
> +			  struct strbuf *out,
> +			  void *pp_cb,
> +			  void **pp_task_cb)
> +{
> +	struct hook_cb_data *hook_cb = pp_cb;
> +	struct hook *run_me = hook_cb->run_me;
> +
> +	if (!run_me)
> +		return 0;

Signal the run_processes_parallel() machinery if we don't have any hooks left
to run. Right now the linked list is always going to be of size 1, but that's
ok :)

> +
> +	cp->no_stdin = 1;
> +	cp->env = hook_cb->options->env.v;
> +	cp->stdout_to_stderr = 1;
> +	cp->trace2_hook_name = hook_cb->hook_name;

These args match identically the ones set up in run_hook_ve. Ok.

> +
> +	/* add command */
> +	strvec_push(&cp->args, run_me->hook_path);
> +
> +	/*
> +	 * add passed-in argv, without expanding - let the user get back
> +	 * exactly what they put in
> +	 */
> +	strvec_pushv(&cp->args, hook_cb->options->args.v);
> +
> +	/* Provide context for errors if necessary */
> +	*pp_task_cb = run_me;
> +
> +	/*
> +	 * This pick_next_hook() will be called again, we're only
> +	 * running one hook, so indicate that no more work will be
> +	 * done.
> +	 */
> +	hook_cb->run_me = NULL;
> +
> +	return 1;
> +}
> +
> +static int notify_start_failure(struct strbuf *out,
> +				void *pp_cb,
> +				void *pp_task_cp)
> +{
> +	struct hook_cb_data *hook_cb = pp_cb;
> +	struct hook *attempted = pp_task_cp;
> +
> +	hook_cb->rc |= 1;

The OR means this will continue to work if the list of hooks is greater than 1.
Ok.

> +
> +	strbuf_addf(out, _("Couldn't start hook '%s'\n"),
> +		    attempted->hook_path);
> +
> +	return 1;

By returning 1, we ask the run_processes_parallel() machinery to give up on
the rest of the hooks. I think that is okay, because the Git operation will not
complete successfully anyway, and because "failed to start process" has more
serious implications for Git's ability to continue starting tasks, compared to
a hook validation failure. Ok.

> +}
> +
> +static int notify_hook_finished(int result,
> +				struct strbuf *out,
> +				void *pp_cb,
> +				void *pp_task_cb)
> +{
> +	struct hook_cb_data *hook_cb = pp_cb;
> +
> +	hook_cb->rc |= result;
> +
> +	return 0;

Conversely, here we could decide to terminate the task list early if we heard
an unhappy hook's return code. However, it's probably more useful for a user
to hear *all* the reasons that their operation failed, rather than just one,
so they don't need to rerun their commit operation 5 times for 5 different hook
failures... :) Fine.

> +}
> +
> +int run_hooks(const char *hook_name, const char *hook_path,
> +	      struct run_hooks_opt *options)
> +{
> +	struct hook my_hook = {
> +		.hook_path = hook_path,
> +	};
> +	struct hook_cb_data cb_data = {
> +		.rc = 0,
> +		.hook_name = hook_name,
> +		.options = options,
> +	};
> +	int jobs = 1;

Even though this is a constant, I find the throwaway variable easier to read
than using a magic 1 in the run_processes_parallel call. I imagine the compiler
gets rid of it, anyways. Thanks.

> +
> +	if (!options)
> +		BUG("a struct run_hooks_opt must be provided to run_hooks");
> +
> +	cb_data.run_me = &my_hook;
> +
> +	run_processes_parallel_tr2(jobs,
> +				   pick_next_hook,
> +				   notify_start_failure,
> +				   notify_hook_finished,
> +				   &cb_data,
> +				   "hook",
> +				   hook_name);
> +
> +	return cb_data.rc;
> +}
> diff --git a/hook.h b/hook.h
> index 6aa36fc7ff9..111c5cf9296 100644
> --- a/hook.h
> +++ b/hook.h
> @@ -1,5 +1,33 @@
>  #ifndef HOOK_H
>  #define HOOK_H
> +#include "strvec.h"
> +
> +struct hook {
> +	/* The path to the hook */
> +	const char *hook_path;
> +};
> +
> +struct run_hooks_opt
> +{
> +	/* Environment vars to be set for each hook */
> +	struct strvec env;
> +
> +	/* Args to be passed to each hook */
> +	struct strvec args;
> +};
> +
> +#define RUN_HOOKS_OPT_INIT { \
> +	.env = STRVEC_INIT, \
> +	.args = STRVEC_INIT, \
> +}
> +
> +struct hook_cb_data {
> +	/* rc reflects the cumulative failure state */
> +	int rc;
> +	const char *hook_name;
> +	struct hook *run_me;
> +	struct run_hooks_opt *options;
> +};
>  
>  /*
>   * Returns the path to the hook file, or NULL if the hook is missing
> @@ -13,4 +41,15 @@ const char *find_hook(const char *name);
>   */
>  int hook_exists(const char *hookname);
>  
> +/**
> + * Clear data from an initialized "struct run_hooks-opt".
Nit, s/run_hooks-opt/run_hooks_opt/
> + */
> +void run_hooks_opt_clear(struct run_hooks_opt *o);
> +
> +/**
> + * Takes an already resolved hook found via find_hook() and runs
> + * it. Does not call run_hooks_opt_clear() for you.
> + */
> +int run_hooks(const char *hookname, const char *hook_path,
> +	      struct run_hooks_opt *options);
>  #endif
> diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh
> new file mode 100755
> index 00000000000..3aea1b105f0
> --- /dev/null
> +++ b/t/t1800-hook.sh
> @@ -0,0 +1,129 @@
> +#!/bin/sh
> +
> +test_description='git-hook command'
> +
> +TEST_PASSES_SANITIZE_LEAK=true
> +. ./test-lib.sh
> +
> +test_expect_success 'git hook usage' '
> +	test_expect_code 129 git hook &&
> +	test_expect_code 129 git hook run &&
> +	test_expect_code 129 git hook run -h &&
> +	test_expect_code 129 git hook run --unknown 2>err &&
> +	grep "unknown option" err
> +'
> +
> +test_expect_success 'git hook run: nonexistent hook' '
> +	cat >stderr.expect <<-\EOF &&
> +	error: cannot find a hook named test-hook
> +	EOF
> +	test_expect_code 1 git hook run test-hook 2>stderr.actual &&
> +	test_cmp stderr.expect stderr.actual

See my earlier worry about this meaning callers still have to check the hookdir.

> +'
> +
> +test_expect_success 'git hook run: basic' '
> +	write_script .git/hooks/test-hook <<-EOF &&
> +	echo Test hook
> +	EOF
> +
> +	cat >expect <<-\EOF &&
> +	Test hook
> +	EOF
> +	git hook run test-hook 2>actual &&
> +	test_cmp expect actual
> +'
> +
> +test_expect_success 'git hook run: stdout and stderr both write to our stderr' '
> +	write_script .git/hooks/test-hook <<-EOF &&
> +	echo >&1 Will end up on stderr
> +	echo >&2 Will end up on stderr
> +	EOF
> +
> +	cat >stderr.expect <<-\EOF &&
> +	Will end up on stderr
> +	Will end up on stderr
> +	EOF
> +	git hook run test-hook >stdout.actual 2>stderr.actual &&
> +	test_cmp stderr.expect stderr.actual &&
> +	test_must_be_empty stdout.actual
> +'
> +
> +test_expect_success 'git hook run: exit codes are passed along' '
> +	write_script .git/hooks/test-hook <<-EOF &&
> +	exit 1
> +	EOF
> +
> +	test_expect_code 1 git hook run test-hook &&
> +
> +	write_script .git/hooks/test-hook <<-EOF &&
> +	exit 2
> +	EOF
> +
> +	test_expect_code 2 git hook run test-hook &&
> +
> +	write_script .git/hooks/test-hook <<-EOF &&
> +	exit 128
> +	EOF
> +
> +	test_expect_code 128 git hook run test-hook &&
> +
> +	write_script .git/hooks/test-hook <<-EOF &&
> +	exit 129
> +	EOF
> +
> +	test_expect_code 129 git hook run test-hook
> +'
> +
> +test_expect_success 'git hook run arg u ments without -- is not allowed' '
> +	test_expect_code 129 git hook run test-hook arg u ments
> +'
> +
> +test_expect_success 'git hook run -- pass arguments' '
> +	write_script .git/hooks/test-hook <<-\EOF &&
> +	echo $1
> +	echo $2
> +	EOF
> +
> +	cat >expect <<-EOF &&
> +	arg
> +	u ments
> +	EOF
> +
> +	git hook run test-hook -- arg "u ments" 2>actual &&
> +	test_cmp expect actual
> +'
> +
> +test_expect_success 'git hook run -- out-of-repo runs excluded' '
> +	write_script .git/hooks/test-hook <<-EOF &&
> +	echo Test hook
> +	EOF
> +
> +	nongit test_must_fail git hook run test-hook
> +'
> +
> +test_expect_success 'git -c core.hooksPath=<PATH> hook run' '
> +	mkdir my-hooks &&
> +	write_script my-hooks/test-hook <<-\EOF &&
> +	echo Hook ran $1 >>actual
> +	EOF
> +
> +	cat >expect <<-\EOF &&
> +	Test hook
> +	Hook ran one
> +	Hook ran two
> +	Hook ran three
> +	Hook ran four
> +	EOF
> +
> +	# Test various ways of specifying the path. See also
> +	# t1350-config-hooks-path.sh
> +	>actual &&
> +	git hook run test-hook -- ignored 2>>actual &&
> +	git -c core.hooksPath=my-hooks hook run test-hook -- one 2>>actual &&
> +	git -c core.hooksPath=my-hooks/ hook run test-hook -- two 2>>actual &&
> +	git -c core.hooksPath="$PWD/my-hooks" hook run test-hook -- three 2>>actual &&
> +	git -c core.hooksPath="$PWD/my-hooks/" hook run test-hook -- four 2>>actual &&
> +	test_cmp expect actual
> +'
> +
> +test_done
> -- 
> 2.33.0.1567.g7b23ce7ed9e

Thanks. Other than my concern about erroring when there is no hook executable,
it looks fine - but that one is serious enough that I'll withhold the
Reviewed-by line.

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 02/13] gc: use hook library for pre-auto-gc hook
  2021-10-15  9:43 ` [PATCH v2 02/13] gc: use hook library for pre-auto-gc hook Ævar Arnfjörð Bjarmason
@ 2021-10-15 16:24   ` Emily Shaffer
  0 siblings, 0 replies; 88+ messages in thread
From: Emily Shaffer @ 2021-10-15 16:24 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Junio C Hamano, Phillip Wood, René Scharfe, Bagas Sanjaya

On Tue, Oct 12, 2021 at 03:30:27PM +0200, Ævar Arnfjörð Bjarmason wrote:
> 
> 
> Move the pre-auto-gc hook away from run-command.h to and over to the
> new hook.h library.
> 
> To do this introduce a simple run_hooks_oneshot() wrapper, we'll be
> using it extensively for these simple cases of wanting to run a single
> hook under a given name, and having it free the memory we allocate for
> us.
> 
> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> ---
>  builtin/gc.c |  3 ++-
>  hook.c       | 20 ++++++++++++++++++++
>  hook.h       | 13 +++++++++++++
>  3 files changed, 35 insertions(+), 1 deletion(-)
> 
> diff --git a/builtin/gc.c b/builtin/gc.c
> index 6b3de3dd514..95939e77f53 100644
> --- a/builtin/gc.c
> +++ b/builtin/gc.c
> @@ -32,6 +32,7 @@
>  #include "remote.h"
>  #include "object-store.h"
>  #include "exec-cmd.h"
> +#include "hook.h"
>  
>  #define FAILED_RUN "failed to run %s"
>  
> @@ -394,7 +395,7 @@ static int need_to_gc(void)
>  	else
>  		return 0;
>  
> -	if (run_hook_le(NULL, "pre-auto-gc", NULL))
> +	if (run_hooks_oneshot("pre-auto-gc", NULL))

I like that the oneshot gives us such a simple replacement for run_hooks_le.

>  		return 0;
>  	return 1;
>  }
> diff --git a/hook.c b/hook.c
> index 240270db64e..bfdc79e2f1a 100644
> --- a/hook.c
> +++ b/hook.c
> @@ -141,3 +141,23 @@ int run_hooks(const char *hook_name, const char *hook_path,
>  
>  	return cb_data.rc;
>  }
> +
> +int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options)
> +{
> +	const char *hook_path;
> +	struct run_hooks_opt hook_opt_scratch = RUN_HOOKS_OPT_INIT;
> +	int ret = 0;
> +
> +	if (!options)
> +		options = &hook_opt_scratch;
> +
> +	hook_path = find_hook(hook_name);
> +	if (!hook_path)
> +		goto cleanup;
> +
> +	ret = run_hooks(hook_name, hook_path, options);
> +cleanup:
> +	run_hooks_opt_clear(options);
> +
> +	return ret;
> +}

Reasonable enough - lookup, run, cleanup. Thanks.

> diff --git a/hook.h b/hook.h
> index 111c5cf9296..a2b8d4fc6bd 100644
> --- a/hook.h
> +++ b/hook.h
> @@ -49,7 +49,20 @@ void run_hooks_opt_clear(struct run_hooks_opt *o);
>  /**
>   * Takes an already resolved hook found via find_hook() and runs
>   * it. Does not call run_hooks_opt_clear() for you.
> + *
> + * See run_hooks_oneshot() for the simpler one-shot API.
>   */
>  int run_hooks(const char *hookname, const char *hook_path,
>  	      struct run_hooks_opt *options);
> +
> +/**
> + * Calls find_hook() on your "hook_name" and runs the hooks (if any)
> + * with run_hooks().
> + *
> + * If "options" is provided calls run_hooks_opt_clear() on it for
> + * you. If "options" is NULL the default options from
> + * RUN_HOOKS_OPT_INIT will be used.
> + */
> +int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options);
> +
>  #endif
> -- 
> 2.33.0.1567.g7b23ce7ed9e
> 

Reviewed-by: Emily Shaffer <emilyshaffer@google.com>

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 03/13] rebase: convert pre-rebase to use hook.h
  2021-10-15  9:43 ` [PATCH v2 03/13] rebase: convert pre-rebase to use hook.h Ævar Arnfjörð Bjarmason
@ 2021-10-15 16:25   ` Emily Shaffer
  0 siblings, 0 replies; 88+ messages in thread
From: Emily Shaffer @ 2021-10-15 16:25 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Junio C Hamano, Phillip Wood, René Scharfe, Bagas Sanjaya

On Tue, Oct 12, 2021 at 03:30:28PM +0200, Ævar Arnfjörð Bjarmason wrote:
> 
> 
> Move the pre-rebase hook away from run-command.h to and over to the
> new hook.h library.
> 
> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Reviewed-by: Emily Shaffer <emilyshaffer@google.com>

> ---
>  builtin/rebase.c | 8 ++++++--
>  1 file changed, 6 insertions(+), 2 deletions(-)
> 
> diff --git a/builtin/rebase.c b/builtin/rebase.c
> index 8c6393f6d78..9a44939d662 100644
> --- a/builtin/rebase.c
> +++ b/builtin/rebase.c
> @@ -28,6 +28,7 @@
>  #include "sequencer.h"
>  #include "rebase-interactive.h"
>  #include "reset.h"
> +#include "hook.h"
>  
>  #define DEFAULT_REFLOG_ACTION "rebase"
>  
> @@ -1305,6 +1306,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>  	char *squash_onto_name = NULL;
>  	int reschedule_failed_exec = -1;
>  	int allow_preemptive_ff = 1;
> +	struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;
>  	struct option builtin_rebase_options[] = {
>  		OPT_STRING(0, "onto", &options.onto_name,
>  			   N_("revision"),
> @@ -2021,9 +2023,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>  	}
>  
>  	/* If a hook exists, give it a chance to interrupt*/
> +	strvec_push(&hook_opt.args, options.upstream_arg);
> +	if (argc)
> +		strvec_push(&hook_opt.args, argv[0]);
>  	if (!ok_to_skip_pre_rebase &&
> -	    run_hook_le(NULL, "pre-rebase", options.upstream_arg,
> -			argc ? argv[0] : NULL, NULL))
> +	    run_hooks_oneshot("pre-rebase", &hook_opt))
>  		die(_("The pre-rebase hook refused to rebase."));
>  
>  	if (options.flags & REBASE_DIFFSTAT) {
> -- 
> 2.33.0.1567.g7b23ce7ed9e
> 

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 04/13] am: convert applypatch to use hook.h
  2021-10-15  9:43 ` [PATCH v2 04/13] am: convert applypatch " Ævar Arnfjörð Bjarmason
@ 2021-10-15 16:25   ` Emily Shaffer
  0 siblings, 0 replies; 88+ messages in thread
From: Emily Shaffer @ 2021-10-15 16:25 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Junio C Hamano, Phillip Wood, René Scharfe, Bagas Sanjaya

On Tue, Oct 12, 2021 at 03:30:29PM +0200, Ævar Arnfjörð Bjarmason wrote:
> 
> 
> Teach pre-applypatch, post-applypatch, and applypatch-msg to use the
> hook.h library instead of the run-command.h library.
> 
> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Reviewed-by: Emily Shaffer <emilyshaffer@google.com>

> ---
>  builtin/am.c | 8 +++++---
>  1 file changed, 5 insertions(+), 3 deletions(-)
> 
> diff --git a/builtin/am.c b/builtin/am.c
> index 3527945a467..2cfe6451b6f 100644
> --- a/builtin/am.c
> +++ b/builtin/am.c
> @@ -446,9 +446,11 @@ static void am_destroy(const struct am_state *state)
>  static int run_applypatch_msg_hook(struct am_state *state)
>  {
>  	int ret;
> +	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
>  
>  	assert(state->msg);
> -	ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL);
> +	strvec_push(&opt.args, am_path(state, "final-commit"));
> +	ret = run_hooks_oneshot("applypatch-msg", &opt);
>  
>  	if (!ret) {
>  		FREE_AND_NULL(state->msg);
> @@ -1609,7 +1611,7 @@ static void do_commit(const struct am_state *state)
>  	const char *reflog_msg, *author, *committer = NULL;
>  	struct strbuf sb = STRBUF_INIT;
>  
> -	if (run_hook_le(NULL, "pre-applypatch", NULL))
> +	if (run_hooks_oneshot("pre-applypatch", NULL))
>  		exit(1);
>  
>  	if (write_cache_as_tree(&tree, 0, NULL))
> @@ -1661,7 +1663,7 @@ static void do_commit(const struct am_state *state)
>  		fclose(fp);
>  	}
>  
> -	run_hook_le(NULL, "post-applypatch", NULL);
> +	run_hooks_oneshot("post-applypatch", NULL);
>  
>  	strbuf_release(&sb);
>  }
> -- 
> 2.33.0.1567.g7b23ce7ed9e
> 

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 05/13] hooks: convert 'post-checkout' hook to hook library
  2021-10-15  9:43 ` [PATCH v2 05/13] hooks: convert 'post-checkout' hook to hook library Ævar Arnfjörð Bjarmason
@ 2021-10-15 16:25   ` Emily Shaffer
  0 siblings, 0 replies; 88+ messages in thread
From: Emily Shaffer @ 2021-10-15 16:25 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Junio C Hamano, Phillip Wood, René Scharfe, Bagas Sanjaya

On Tue, Oct 12, 2021 at 03:30:30PM +0200, Ævar Arnfjörð Bjarmason wrote:
> 
> 
> Move the running of the 'post-checkout' hook away from run-command.h
> to the new hook.h library. For "worktree" this requires a change to it
> to run the hooks from a given directory.
> 
> We could strictly speaking skip the "absolute_path" flag and just
> check if "dir" is specified, but let's split them up for clarity, as
> well as for any future user who'd like to set "dir" but not implicitly
> change the argument to an absolute path.
> 
> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> --- a/read-cache.c
> +++ b/read-cache.c
> @@ -28,6 +28,7 @@
>  #include "sparse-index.h"
>  #include "csum-file.h"
>  #include "promisor-remote.h"
> +#include "hook.h"

Hum, is this a stray line? I don't see any other change in read-cache.c?

Otherwise, the change looks reasonable to me.

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 06/13] merge: convert post-merge to use hook.h
  2021-10-15  9:43 ` [PATCH v2 06/13] merge: convert post-merge to use hook.h Ævar Arnfjörð Bjarmason
@ 2021-10-15 16:26   ` Emily Shaffer
  0 siblings, 0 replies; 88+ messages in thread
From: Emily Shaffer @ 2021-10-15 16:26 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Junio C Hamano, Phillip Wood, René Scharfe, Bagas Sanjaya


On Tue, Oct 12, 2021 at 03:30:31PM +0200, Ævar Arnfjörð Bjarmason wrote:
> 
> 
> Teach post-merge to use the hook.h library instead of the
> run-command.h library to run hooks.
> 
> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Reviewed-by: Emily Shaffer <emilyshaffer@google.com>

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 07/13] git hook run: add an --ignore-missing flag
  2021-10-15  9:43 ` [PATCH v2 07/13] git hook run: add an --ignore-missing flag Ævar Arnfjörð Bjarmason
@ 2021-10-15 16:26   ` Emily Shaffer
  0 siblings, 0 replies; 88+ messages in thread
From: Emily Shaffer @ 2021-10-15 16:26 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Junio C Hamano, Phillip Wood, René Scharfe, Bagas Sanjaya


On Tue, Oct 12, 2021 at 03:30:32PM +0200, Ævar Arnfjörð Bjarmason wrote:
> 
> For certain one-shot hooks we'd like to optimistically run them, and
> not complain if they don't exist. This will be used by send-email in a
> subsequent commit.

Aha, this is what I was looking for in the earlier commit. Feel free to
disregard those complaints. ;)

> 
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Reviewed-by: Emily Shaffer <emilyshaffer@google.com>

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 08/13] send-email: use 'git hook run' for 'sendemail-validate'
  2021-10-15  9:43 ` [PATCH v2 08/13] send-email: use 'git hook run' for 'sendemail-validate' Ævar Arnfjörð Bjarmason
@ 2021-10-15 17:07   ` Emily Shaffer
  0 siblings, 0 replies; 88+ messages in thread
From: Emily Shaffer @ 2021-10-15 17:07 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Junio C Hamano, Phillip Wood, René Scharfe, Bagas Sanjaya

On Fri, Oct 15, 2021 at 11:43:36AM +0200, Ævar Arnfjörð Bjarmason wrote:
> 
> 
> Change the "sendmail-validate" hook to be run via the "git hook run"
> wrapper instead of via a direct invocation.
> 
> This is the smallest possibly change to get "send-email" using "git
> hook run". We still check the hook itself with "-x", and set a
> "GIT_DIR" variable, both of which are asserted by our tests. We'll
> need to get rid of this special behavior if we start running N hooks,
> but for now let's be as close to bug-for-bug compatible as possible.
> 
> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Someday we can delete a bunch more in there and on that day we can
celebrate. But the rationale for making the change minimal LGTM.

Reviewed-by: Emily Shaffer <emilyshaffer@google.com>

> ---
>  git-send-email.perl   | 22 ++++++++++++++--------
>  t/t9001-send-email.sh |  4 ++--
>  2 files changed, 16 insertions(+), 10 deletions(-)
> 
> diff --git a/git-send-email.perl b/git-send-email.perl
> index 5262d88ee32..4c20c0bbb79 100755
> --- a/git-send-email.perl
> +++ b/git-send-email.perl
> @@ -195,13 +195,13 @@ sub format_2822_time {
>  my $editor;
>  
>  sub system_or_msg {
> -	my ($args, $msg) = @_;
> +	my ($args, $msg, $cmd_name) = @_;
>  	system(@$args);
>  	my $signalled = $? & 127;
>  	my $exit_code = $? >> 8;
>  	return unless $signalled or $exit_code;
>  
> -	my @sprintf_args = ($args->[0], $exit_code);
> +	my @sprintf_args = ($cmd_name ? $cmd_name : $args->[0], $exit_code);
>  	if (defined $msg) {
>  		# Quiet the 'redundant' warning category, except we
>  		# need to support down to Perl 5.8, so we can't do a
> @@ -2039,10 +2039,10 @@ sub validate_patch {
>  	my ($fn, $xfer_encoding) = @_;
>  
>  	if ($repo) {
> +		my $hook_name = 'sendemail-validate';
>  		my $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks');
>  		require File::Spec;
> -		my $validate_hook = File::Spec->catfile($hooks_path,
> -					    'sendemail-validate');
> +		my $validate_hook = File::Spec->catfile($hooks_path, $hook_name);
>  		my $hook_error;
>  		if (-x $validate_hook) {
>  			require Cwd;
> @@ -2052,13 +2052,19 @@ sub validate_patch {
>  			chdir($repo->wc_path() or $repo->repo_path())
>  				or die("chdir: $!");
>  			local $ENV{"GIT_DIR"} = $repo->repo_path();
> -			$hook_error = system_or_msg([$validate_hook, $target]);
> +			my @cmd = ("git", "hook", "run", "--ignore-missing",
> +				    $hook_name, "--");
> +			my @cmd_msg = (@cmd, "<patch>");
> +			my @cmd_run = (@cmd, $target);
> +			$hook_error = system_or_msg(\@cmd_run, undef, "@cmd_msg");
>  			chdir($cwd_save) or die("chdir: $!");
>  		}
>  		if ($hook_error) {
> -			die sprintf(__("fatal: %s: rejected by sendemail-validate hook\n" .
> -				       "%s\n" .
> -				       "warning: no patches were sent\n"), $fn, $hook_error);
> +			$hook_error = sprintf(__("fatal: %s: rejected by %s hook\n" .
> +						 $hook_error . "\n" .
> +						 "warning: no patches were sent\n"),
> +					      $fn, $hook_name);
> +			die $hook_error;
>  		}
>  	}
>  
> diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
> index aa0c20499ba..84d0f40d76a 100755
> --- a/t/t9001-send-email.sh
> +++ b/t/t9001-send-email.sh
> @@ -539,7 +539,7 @@ test_expect_success $PREREQ "--validate respects relative core.hooksPath path" '
>  	test_path_is_file my-hooks.ran &&
>  	cat >expect <<-EOF &&
>  	fatal: longline.patch: rejected by sendemail-validate hook
> -	fatal: command '"'"'my-hooks/sendemail-validate'"'"' died with exit code 1
> +	fatal: command '"'"'git hook run --ignore-missing sendemail-validate -- <patch>'"'"' died with exit code 1
>  	warning: no patches were sent
>  	EOF
>  	test_cmp expect actual
> @@ -558,7 +558,7 @@ test_expect_success $PREREQ "--validate respects absolute core.hooksPath path" '
>  	test_path_is_file my-hooks.ran &&
>  	cat >expect <<-EOF &&
>  	fatal: longline.patch: rejected by sendemail-validate hook
> -	fatal: command '"'"'$hooks_path/sendemail-validate'"'"' died with exit code 1
> +	fatal: command '"'"'git hook run --ignore-missing sendemail-validate -- <patch>'"'"' died with exit code 1
>  	warning: no patches were sent
>  	EOF
>  	test_cmp expect actual
> -- 
> 2.33.1.1338.g20da966911a
> 

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 09/13] git-p4: use 'git hook' to run hooks
  2021-10-15  9:43 ` [PATCH v2 09/13] git-p4: use 'git hook' to run hooks Ævar Arnfjörð Bjarmason
@ 2021-10-15 17:08   ` Emily Shaffer
  0 siblings, 0 replies; 88+ messages in thread
From: Emily Shaffer @ 2021-10-15 17:08 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Junio C Hamano, Phillip Wood, René Scharfe, Bagas Sanjaya

On Fri, Oct 15, 2021 at 11:43:37AM +0200, Ævar Arnfjörð Bjarmason wrote:
> 
> 
> Instead of duplicating the behavior of run-command.h:run_hook_le() in
> Python, we can directly call 'git hook run'. We emulate the existence
> check with the --ignore-missing flag.
> 
> As this is the last hook execution in git.git to not go through "git
> hook run" or the hook.[ch] library we can now be absolutely sure that
> our assertion in hook.c that only hooks known by the generated (from
> githooks(5)) hook-list.h are permitted.
> 
> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> ---
>  git-p4.py | 72 ++++++-------------------------------------------------
>  1 file changed, 7 insertions(+), 65 deletions(-)
> 
> diff --git a/git-p4.py b/git-p4.py
> index 2b4500226aa..1f24cbf0bca 100755
> --- a/git-p4.py
> +++ b/git-p4.py
> @@ -207,71 +207,13 @@ def decode_path(path):
>          return path
>  
>  def run_git_hook(cmd, param=[]):
> -    """Execute a hook if the hook exists."""
> -    if verbose:
> -        sys.stderr.write("Looking for hook: %s\n" % cmd)
> -        sys.stderr.flush()
> -
> -    hooks_path = gitConfig("core.hooksPath")
> -    if len(hooks_path) <= 0:
> -        hooks_path = os.path.join(os.environ["GIT_DIR"], "hooks")
> -
> -    if not isinstance(param, list):
> -        param=[param]
> -
> -    # resolve hook file name, OS depdenent
> -    hook_file = os.path.join(hooks_path, cmd)
> -    if platform.system() == 'Windows':
> -        if not os.path.isfile(hook_file):
> -            # look for the file with an extension
> -            files = glob.glob(hook_file + ".*")
> -            if not files:
> -                return True
> -            files.sort()
> -            hook_file = files.pop()
> -            while hook_file.upper().endswith(".SAMPLE"):
> -                # The file is a sample hook. We don't want it
> -                if len(files) > 0:
> -                    hook_file = files.pop()
> -                else:
> -                    return True
> -
> -    if not os.path.isfile(hook_file) or not os.access(hook_file, os.X_OK):
> -        return True
> -
> -    return run_hook_command(hook_file, param) == 0
> -
> -def run_hook_command(cmd, param):
> -    """Executes a git hook command
> -       cmd = the command line file to be executed. This can be
> -       a file that is run by OS association.
> -
> -       param = a list of parameters to pass to the cmd command
> -
> -       On windows, the extension is checked to see if it should
> -       be run with the Git for Windows Bash shell.  If there
> -       is no file extension, the file is deemed a bash shell
> -       and will be handed off to sh.exe. Otherwise, Windows
> -       will be called with the shell to handle the file assocation.
> -
> -       For non Windows operating systems, the file is called
> -       as an executable.
> -    """
> -    cli = [cmd] + param
> -    use_shell = False
> -    if platform.system() == 'Windows':
> -        (root,ext) = os.path.splitext(cmd)
> -        if ext == "":
> -            exe_path = os.environ.get("EXEPATH")
> -            if exe_path is None:
> -                exe_path = ""
> -            else:
> -                exe_path = os.path.join(exe_path, "bin")
> -            cli = [os.path.join(exe_path, "SH.EXE")] + cli
> -        else:
> -            use_shell = True
> -    return subprocess.call(cli, shell=use_shell)
> -
> +    """args are specified with -a <arg> -a <arg> -a <arg>"""

Ah, the comment is misleading now, since we're adding args after the --.

> +    args = ['git', 'hook', 'run', '--ignore-missing', cmd]
> +    if param:
> +        args.append("--")
> +        for p in param:
> +            args.append(p)
> +    return subprocess.call(args) == 0
>  
>  def write_pipe(c, stdin):
>      if verbose:
> -- 
> 2.33.1.1338.g20da966911a
> 

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 10/13] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h
  2021-10-15  9:43 ` [PATCH v2 10/13] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h Ævar Arnfjörð Bjarmason
@ 2021-10-15 17:15   ` Emily Shaffer
  0 siblings, 0 replies; 88+ messages in thread
From: Emily Shaffer @ 2021-10-15 17:15 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Junio C Hamano, Phillip Wood, René Scharfe, Bagas Sanjaya

On Fri, Oct 15, 2021 at 11:43:38AM +0200, Ævar Arnfjörð Bjarmason wrote:
> 
> 
> Move these hooks hook away from run-command.h to and over to the new
> hook.h library.
> 
> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Very straightforward.
Reviewed-by: Emily Shaffer <emilyshaffer@google.com>

> ---
>  commit.c | 15 ++++++++-------
>  1 file changed, 8 insertions(+), 7 deletions(-)
> 
> diff --git a/commit.c b/commit.c
> index 551de4903c1..4e7cbd7585d 100644
> --- a/commit.c
> +++ b/commit.c
> @@ -21,6 +21,7 @@
>  #include "commit-reach.h"
>  #include "run-command.h"
>  #include "shallow.h"
> +#include "hook.h"
>  
>  static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
>  
> @@ -1700,22 +1701,22 @@ size_t ignore_non_trailer(const char *buf, size_t len)
>  int run_commit_hook(int editor_is_used, const char *index_file,
>  		    const char *name, ...)
>  {
> -	struct strvec hook_env = STRVEC_INIT;
> +	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
>  	va_list args;
> -	int ret;
> +	const char *arg;
>  
> -	strvec_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file);
> +	strvec_pushf(&opt.env, "GIT_INDEX_FILE=%s", index_file);
>  
>  	/*
>  	 * Let the hook know that no editor will be launched.
>  	 */
>  	if (!editor_is_used)
> -		strvec_push(&hook_env, "GIT_EDITOR=:");
> +		strvec_push(&opt.env, "GIT_EDITOR=:");
>  
>  	va_start(args, name);
> -	ret = run_hook_ve(hook_env.v, name, args);
> +	while ((arg = va_arg(args, const char *)))
> +		strvec_push(&opt.args, arg);
>  	va_end(args);
> -	strvec_clear(&hook_env);
>  
> -	return ret;
> +	return run_hooks_oneshot(name, &opt);
>  }
> -- 
> 2.33.1.1338.g20da966911a
> 

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 11/13] read-cache: convert post-index-change to use hook.h
  2021-10-15  9:43 ` [PATCH v2 11/13] read-cache: convert post-index-change to use hook.h Ævar Arnfjörð Bjarmason
@ 2021-10-15 17:17   ` Emily Shaffer
  0 siblings, 0 replies; 88+ messages in thread
From: Emily Shaffer @ 2021-10-15 17:17 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Junio C Hamano, Phillip Wood, René Scharfe, Bagas Sanjaya

On Fri, Oct 15, 2021 at 11:43:39AM +0200, Ævar Arnfjörð Bjarmason wrote:
> 
> 
> Move the post-index-change hook away from run-command.h to and over to
> the new hook.h library.
> 
> This removes the last direct user of "run_hook_ve()" outside of
> run-command.c ("run_hook_le()" still uses it). So we can make the
> function static now. A subsequent commit will remove this code
> entirely when "run_hook_le()" itself goes away.

\o/

> 
> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Reviewed-by: Emily Shaffer <emilyshaffer@google.com>

> ---
>  read-cache.c  | 10 +++++++---
>  run-command.c |  2 +-
>  run-command.h |  1 -
>  3 files changed, 8 insertions(+), 5 deletions(-)
> 
> diff --git a/read-cache.c b/read-cache.c
> index 9773118d078..84cc3d88196 100644
> --- a/read-cache.c
> +++ b/read-cache.c
> @@ -3087,6 +3087,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
>  {
>  	int ret;
>  	int was_full = !istate->sparse_index;
> +	struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;
>  
>  	ret = convert_to_sparse(istate, 0);
>  
> @@ -3115,9 +3116,12 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
>  	else
>  		ret = close_lock_file_gently(lock);
>  
> -	run_hook_le(NULL, "post-index-change",
> -			istate->updated_workdir ? "1" : "0",
> -			istate->updated_skipworktree ? "1" : "0", NULL);
> +	strvec_pushl(&hook_opt.args,
> +		     istate->updated_workdir ? "1" : "0",
> +		     istate->updated_skipworktree ? "1" : "0",
> +		     NULL);
> +	run_hooks_oneshot("post-index-change", &hook_opt);
> +
>  	istate->updated_workdir = 0;
>  	istate->updated_skipworktree = 0;
>  
> diff --git a/run-command.c b/run-command.c
> index 7ef5cc712a9..d92e670c8ed 100644
> --- a/run-command.c
> +++ b/run-command.c
> @@ -1323,7 +1323,7 @@ int async_with_fork(void)
>  #endif
>  }
>  
> -int run_hook_ve(const char *const *env, const char *name, va_list args)
> +static int run_hook_ve(const char *const *env, const char *name, va_list args)
>  {
>  	struct child_process hook = CHILD_PROCESS_INIT;
>  	const char *p;
> diff --git a/run-command.h b/run-command.h
> index 49878262584..3fa7454cf8a 100644
> --- a/run-command.h
> +++ b/run-command.h
> @@ -239,7 +239,6 @@ int run_command(struct child_process *);
>   */
>  LAST_ARG_MUST_BE_NULL
>  int run_hook_le(const char *const *env, const char *name, ...);
> -int run_hook_ve(const char *const *env, const char *name, va_list args);
>  
>  /*
>   * Trigger an auto-gc
> -- 
> 2.33.1.1338.g20da966911a
> 

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 13/13] run-command: remove old run_hook_{le,ve}() hook API
  2021-10-15  9:43 ` [PATCH v2 13/13] run-command: remove old run_hook_{le,ve}() hook API Ævar Arnfjörð Bjarmason
@ 2021-10-15 17:18   ` Emily Shaffer
  0 siblings, 0 replies; 88+ messages in thread
From: Emily Shaffer @ 2021-10-15 17:18 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Junio C Hamano, Phillip Wood, René Scharfe, Bagas Sanjaya

On Fri, Oct 15, 2021 at 11:43:41AM +0200, Ævar Arnfjörð Bjarmason wrote:
> 
> 
> The new hook.h library has replaced all run-command.h hook-related
> functionality. So let's delete this dead code.
> 
> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Reviewed-by: Emily Shaffer <emilyshaffer@google.com>

Thanks.

> ---
>  run-command.c | 32 --------------------------------
>  run-command.h | 16 ----------------
>  2 files changed, 48 deletions(-)
> 
> diff --git a/run-command.c b/run-command.c
> index d92e670c8ed..8a21ff525f3 100644
> --- a/run-command.c
> +++ b/run-command.c
> @@ -1323,38 +1323,6 @@ int async_with_fork(void)
>  #endif
>  }
>  
> -static int run_hook_ve(const char *const *env, const char *name, va_list args)
> -{
> -	struct child_process hook = CHILD_PROCESS_INIT;
> -	const char *p;
> -
> -	p = find_hook(name);
> -	if (!p)
> -		return 0;
> -
> -	strvec_push(&hook.args, p);
> -	while ((p = va_arg(args, const char *)))
> -		strvec_push(&hook.args, p);
> -	hook.env = env;
> -	hook.no_stdin = 1;
> -	hook.stdout_to_stderr = 1;
> -	hook.trace2_hook_name = name;
> -
> -	return run_command(&hook);
> -}
> -
> -int run_hook_le(const char *const *env, const char *name, ...)
> -{
> -	va_list args;
> -	int ret;
> -
> -	va_start(args, name);
> -	ret = run_hook_ve(env, name, args);
> -	va_end(args);
> -
> -	return ret;
> -}
> -
>  struct io_pump {
>  	/* initialized by caller */
>  	int fd;
> diff --git a/run-command.h b/run-command.h
> index 3fa7454cf8a..59e1fbff64c 100644
> --- a/run-command.h
> +++ b/run-command.h
> @@ -224,22 +224,6 @@ int finish_command_in_signal(struct child_process *);
>   */
>  int run_command(struct child_process *);
>  
> -/**
> - * Run a hook.
> - * The first argument is a pathname to an index file, or NULL
> - * if the hook uses the default index file or no index is needed.
> - * The second argument is the name of the hook.
> - * The further arguments correspond to the hook arguments.
> - * The last argument has to be NULL to terminate the arguments list.
> - * If the hook does not exist or is not executable, the return
> - * value will be zero.
> - * If it is executable, the hook will be executed and the exit
> - * status of the hook is returned.
> - * On execution, .stdout_to_stderr and .no_stdin will be set.
> - */
> -LAST_ARG_MUST_BE_NULL
> -int run_hook_le(const char *const *env, const char *name, ...);
> -
>  /*
>   * Trigger an auto-gc
>   */
> -- 
> 2.33.1.1338.g20da966911a
> 

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 01/13] hook: add 'run' subcommand
  2021-10-15 16:24   ` Emily Shaffer
@ 2021-10-15 17:53     ` Eric Sunshine
  2021-10-19 23:08     ` Ævar Arnfjörð Bjarmason
  1 sibling, 0 replies; 88+ messages in thread
From: Eric Sunshine @ 2021-10-15 17:53 UTC (permalink / raw)
  To: Emily Shaffer
  Cc: Ævar Arnfjörð Bjarmason, Git List, Junio C Hamano,
	Phillip Wood, René Scharfe, Bagas Sanjaya

On Fri, Oct 15, 2021 at 12:24 PM Emily Shaffer <emilyshaffer@google.com> wrote:
> On Tue, Oct 12, 2021 at 03:30:26PM +0200, Ævar Arnfjörð Bjarmason wrote:
> > +     if (argc > 1 && strcmp(argv[1], "--") &&
> > +         strcmp(argv[1], "--end-of-options"))
>
> --end-of-options isn't documented anywhere, right? Fine, I don't mind, but it's
> interesting to support it "secretly".

This option is documented in Documentation/gitcli.txt (i.e. `git help cli`).

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion
  2021-10-15  9:43 [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                   ` (13 preceding siblings ...)
  2021-10-15 16:23 ` [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion Emily Shaffer
@ 2021-10-16  0:58 ` Junio C Hamano
  2021-10-16  5:22   ` Eric Sunshine
  2021-10-16  5:47   ` Ævar Arnfjörð Bjarmason
  2021-10-19 23:20 ` [PATCH v3 " Ævar Arnfjörð Bjarmason
  15 siblings, 2 replies; 88+ messages in thread
From: Junio C Hamano @ 2021-10-16  0:58 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Phillip Wood, René Scharfe, Emily Shaffer, Bagas Sanjaya

Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:

> Part 2 of the greater configurable hook saga, starting by converting
> some existing simple hooks to the new hook.[ch] library and "git hook
> run" utility.
>
> See the v1 [1] CL for more context. I've dropped the git-p4 folks from
> the CC list, your feedback on git-p4 would still be very appreciated,
> but I don't think I need to re-spam you for every re-roll of this.


This, when merged to 'seen', seems to fail so many tests to make me
question my sanity.  Something is not playing with it well.

Queued on top of a merge of ab/config-based-hooks-1 into 'master';
when tested alone, without any other topics in 'seen', it seems to
fare much better.  Only t2400-worktree-add.sh fails at 58-59 and 62.



Test Summary Report
-------------------
t1092-sparse-checkout-compatibility.sh           (Wstat: 256 Tests: 36 Failed: 2)
  Failed tests:  23, 25
  Non-zero exit status: 1
t5572-pull-submodule.sh                          (Wstat: 256 Tests: 64 Failed: 2)
  Failed tests:  61, 63
  Non-zero exit status: 1
t3432-rebase-fast-forward.sh                     (Wstat: 256 Tests: 225 Failed: 203)
  Failed tests:  15-85, 87-170, 172-201, 203, 205, 207, 209
                211, 213-225
  Non-zero exit status: 1
t7406-submodule-update.sh                        (Wstat: 256 Tests: 58 Failed: 9)
  Failed tests:  14-22
  Non-zero exit status: 1
t3404-rebase-interactive.sh                      (Wstat: 256 Tests: 119 Failed: 93)
  Failed tests:  4-6, 8-9, 14-21, 24-42, 44-47, 49, 52-59
                61, 63-69, 71-78, 80-86, 89, 92-104, 106
                108, 110-118
  Non-zero exit status: 1
t3426-rebase-submodule.sh                        (Wstat: 256 Tests: 29 Failed: 18)
  Failed tests:  1-6, 11-13, 15-20, 25-27
  Non-zero exit status: 1
t0021-conversion.sh                              (Wstat: 256 Tests: 41 Failed: 1)
  Failed test:  18
  Non-zero exit status: 1
t5520-pull.sh                                    (Wstat: 256 Tests: 70 Failed: 26)
  Failed tests:  19, 23-24, 26-29, 40-41, 43, 45-46, 49-52
                55, 57-62, 67, 69-70
  Non-zero exit status: 1
t5533-push-cas.sh                                (Wstat: 256 Tests: 23 Failed: 2)
  Failed tests:  21-22
  Non-zero exit status: 1
t3200-branch.sh                                  (Wstat: 256 Tests: 145 Failed: 2)
  Failed tests:  140-141
  Non-zero exit status: 1
t3421-rebase-topology-linear.sh                  (Wstat: 256 Tests: 64 Failed: 50)
  Failed tests:  2-4, 9-16, 22-27, 29-33, 36-42, 44-64
  Non-zero exit status: 1
t2400-worktree-add.sh                            (Wstat: 256 Tests: 71 Failed: 6)
  Failed tests:  41, 45-46, 58-59, 62
  Non-zero exit status: 1
t7512-status-help.sh                             (Wstat: 256 Tests: 44 Failed: 20)
  Failed tests:  5-6, 8-12, 14-22, 28, 41-43
  Non-zero exit status: 1
t3420-rebase-autostash.sh                        (Wstat: 256 Tests: 42 Failed: 38)
  Failed tests:  2-3, 5-36, 39-42
  Non-zero exit status: 1
t7601-merge-pull-config.sh                       (Wstat: 256 Tests: 62 Failed: 11)
  Failed tests:  15, 25-26, 28-29, 33-36, 42-43
  Non-zero exit status: 1
t3430-rebase-merges.sh                           (Wstat: 256 Tests: 25 Failed: 21)
  Failed tests:  2, 5-10, 12-25
  Non-zero exit status: 1
t9903-bash-prompt.sh                             (Wstat: 256 Tests: 66 Failed: 47)
  Failed tests:  14-35, 37, 40-52, 54-63, 66
  Non-zero exit status: 1
t3415-rebase-autosquash.sh                       (Wstat: 256 Tests: 24 Failed: 23)
  Failed tests:  2-24
  Non-zero exit status: 1
t3418-rebase-continue.sh                         (Wstat: 256 Tests: 29 Failed: 19)
  Failed tests:  2-3, 6-9, 11-19, 26-29
  Non-zero exit status: 1
t3433-rebase-across-mode-change.sh               (Wstat: 256 Tests: 4 Failed: 3)
  Failed tests:  2-4
  Non-zero exit status: 1
t3400-rebase.sh                                  (Wstat: 256 Tests: 35 Failed: 23)
  Failed tests:  4-6, 10-13, 18-33
  Non-zero exit status: 1
t7528-signed-commit-ssh.sh                       (Wstat: 256 Tests: 23 Failed: 14)
  Failed tests:  1-5, 7, 10-17
  Non-zero exit status: 1
t3435-rebase-gpg-sign.sh                         (Wstat: 256 Tests: 20 Failed: 17)
  Failed tests:  3-11, 13-20
  Non-zero exit status: 1
t7510-signed-commit.sh                           (Wstat: 256 Tests: 25 Failed: 19)
  Failed tests:  1-6, 8, 11-19, 23-25
  Non-zero exit status: 1
t3431-rebase-fork-point.sh                       (Wstat: 256 Tests: 26 Failed: 23)
  Failed tests:  2-19, 22-26
  Non-zero exit status: 1
t5407-post-rewrite-hook.sh                       (Wstat: 256 Tests: 16 Failed: 13)
  Failed tests:  4-16
  Non-zero exit status: 1
t7505-prepare-commit-msg-hook.sh                 (Wstat: 256 Tests: 20 Failed: 1)
  Failed test:  14
  Non-zero exit status: 1
t3901-i18n-patch.sh                              (Wstat: 256 Tests: 20 Failed: 8)
  Failed tests:  4-7, 12-15
  Non-zero exit status: 1
t3900-i18n-commit.sh                             (Wstat: 256 Tests: 38 Failed: 6)
  Failed tests:  33-38
  Non-zero exit status: 1
t2012-checkout-last.sh                           (Wstat: 256 Tests: 22 Failed: 4)
  Failed tests:  19-22
  Non-zero exit status: 1
t3436-rebase-more-options.sh                     (Wstat: 256 Tests: 17 Failed: 15)
  Failed tests:  2-16
  Non-zero exit status: 1
t3437-rebase-fixup-options.sh                    (Wstat: 256 Tests: 11 Failed: 10)
  Failed tests:  2-11
  Non-zero exit status: 1
t3407-rebase-abort.sh                            (Wstat: 256 Tests: 15 Failed: 5)
  Failed tests:  3, 5, 10-12
  Non-zero exit status: 1
t7504-commit-msg-hook.sh                         (Wstat: 256 Tests: 25 Failed: 1)
  Failed test:  25
  Non-zero exit status: 1
t7030-verify-tag.sh                              (Wstat: 256 Tests: 15 Failed: 6)
  Failed tests:  1, 3, 8-9, 11-12
  Non-zero exit status: 1
t3402-rebase-merge.sh                            (Wstat: 256 Tests: 14 Failed: 12)
  Failed tests:  3-14
  Non-zero exit status: 1
t3403-rebase-skip.sh                             (Wstat: 256 Tests: 20 Failed: 12)
  Failed tests:  4-5, 8, 11-19
  Non-zero exit status: 1
t3412-rebase-root.sh                             (Wstat: 256 Tests: 25 Failed: 12)
  Failed tests:  4, 6-8, 10-13, 19, 21-22, 24
  Non-zero exit status: 1
t3425-rebase-topology-merges.sh                  (Wstat: 256 Tests: 13 Failed: 12)
  Failed tests:  2-13
  Non-zero exit status: 1
t7031-verify-tag-signed-ssh.sh                   (Wstat: 256 Tests: 8 Failed: 6)
  Failed tests:  1-6
  Non-zero exit status: 1
t3424-rebase-empty.sh                            (Wstat: 256 Tests: 15 Failed: 13)
  Failed tests:  3-15
  Non-zero exit status: 1
t7517-per-repo-email.sh                          (Wstat: 256 Tests: 16 Failed: 3)
  Failed tests:  7, 9, 11
  Non-zero exit status: 1
t6427-diff3-conflict-markers.sh                  (Wstat: 256 Tests: 9 Failed: 2)
  Failed tests:  7-8
  Non-zero exit status: 1
t3416-rebase-onto-threedots.sh                   (Wstat: 256 Tests: 13 Failed: 6)
  Failed tests:  2-3, 5-6, 10, 12
  Non-zero exit status: 1
t3406-rebase-message.sh                          (Wstat: 256 Tests: 14 Failed: 13)
  Failed tests:  2-14
  Non-zero exit status: 1
t3413-rebase-hook.sh                             (Wstat: 256 Tests: 15 Failed: 10)
  Failed tests:  2-3, 5-10, 14-15
  Non-zero exit status: 1
t3401-rebase-and-am-rename.sh                    (Wstat: 256 Tests: 10 Failed: 5)
  Failed tests:  2, 4, 7-9
  Non-zero exit status: 1
t3434-rebase-i18n.sh                             (Wstat: 256 Tests: 6 Failed: 5)
  Failed tests:  2-6
  Non-zero exit status: 1
t7402-submodule-rebase.sh                        (Wstat: 256 Tests: 6 Failed: 3)
  Failed tests:  2-3, 6
  Non-zero exit status: 1
t3429-rebase-edit-todo.sh                        (Wstat: 256 Tests: 6 Failed: 2)
  Failed tests:  3-4
  Non-zero exit status: 1
t3419-rebase-patch-id.sh                         (Wstat: 256 Tests: 5 Failed: 1)
  Failed test:  4
  Non-zero exit status: 1
t3428-rebase-signoff.sh                          (Wstat: 256 Tests: 6 Failed: 6)
  Failed tests:  1-6
  Non-zero exit status: 1
t5403-post-checkout-hook.sh                      (Wstat: 256 Tests: 8 Failed: 2)
  Failed tests:  6-7
  Non-zero exit status: 1
t3427-rebase-subtree.sh                          (Wstat: 256 Tests: 3 Failed: 2)
  Failed tests:  2-3
  Non-zero exit status: 1
t3423-rebase-reword.sh                           (Wstat: 256 Tests: 3 Failed: 2)
  Failed tests:  2-3
  Non-zero exit status: 1
t3417-rebase-whitespace-fix.sh                   (Wstat: 256 Tests: 4 Failed: 4)
  Failed tests:  1-4
  Non-zero exit status: 1
t3405-rebase-malformed.sh                        (Wstat: 256 Tests: 5 Failed: 3)
  Failed tests:  3-5
  Non-zero exit status: 1
t3408-rebase-multi-line.sh                       (Wstat: 256 Tests: 2 Failed: 1)
  Failed test:  2
  Non-zero exit status: 1
Files=942, Tests=24498, 113 wallclock secs ( 8.74 usr  2.04 sys + 726.97 cusr 424.82 csys = 1162.57 CPU)
Result: FAIL
g

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion
  2021-10-16  0:58 ` Junio C Hamano
@ 2021-10-16  5:22   ` Eric Sunshine
  2021-10-16  5:47   ` Ævar Arnfjörð Bjarmason
  1 sibling, 0 replies; 88+ messages in thread
From: Eric Sunshine @ 2021-10-16  5:22 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Ævar Arnfjörð Bjarmason, Git List, Phillip Wood,
	René Scharfe, Emily Shaffer, Bagas Sanjaya

On Fri, Oct 15, 2021 at 8:58 PM Junio C Hamano <gitster@pobox.com> wrote:
> Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:
> > Part 2 of the greater configurable hook saga, starting by converting
> > some existing simple hooks to the new hook.[ch] library and "git hook
> > run" utility.
>
> This, when merged to 'seen', seems to fail so many tests to make me
> question my sanity.  Something is not playing with it well.
>
> Queued on top of a merge of ab/config-based-hooks-1 into 'master';
> when tested alone, without any other topics in 'seen', it seems to
> fare much better.  Only t2400-worktree-add.sh fails at 58-59 and 62.

For what it's worth, `git worktree add` is special in that it has to
run the `post-checkout` hook in the newly-created worktree, not in the
worktree in which the `git worktree add` command was issued.

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion
  2021-10-16  0:58 ` Junio C Hamano
  2021-10-16  5:22   ` Eric Sunshine
@ 2021-10-16  5:47   ` Ævar Arnfjörð Bjarmason
  2021-10-16 18:04     ` Junio C Hamano
  1 sibling, 1 reply; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-16  5:47 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Phillip Wood, René Scharfe, Emily Shaffer, Bagas Sanjaya


On Fri, Oct 15 2021, Junio C Hamano wrote:

> Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:
>
>> Part 2 of the greater configurable hook saga, starting by converting
>> some existing simple hooks to the new hook.[ch] library and "git hook
>> run" utility.
>>
>> See the v1 [1] CL for more context. I've dropped the git-p4 folks from
>> the CC list, your feedback on git-p4 would still be very appreciated,
>> but I don't think I need to re-spam you for every re-roll of this.
>
>
> This, when merged to 'seen', seems to fail so many tests to make me
> question my sanity.  Something is not playing with it well.
>
> Queued on top of a merge of ab/config-based-hooks-1 into 'master';
> when tested alone, without any other topics in 'seen', it seems to
> fare much better.  Only t2400-worktree-add.sh fails at 58-59 and 62.

I haven't looked into worktree-add.sh failure (and from what Eric says
it seems unrelated), but all the rest is due to a mismerge of
reset.c. The diff here at the end on top of "seen" fixes it[1].

I'm able to reproduce it with:

    $ git reference 978c287ca92 # just a "show" alias...
    978c287ca92 (hooks: convert 'post-checkout' hook to hook library, 2021-10-15)
    $ git checkout origin/seen^1; git -c rerere.enabled=false cherry-pick 978c287ca92

Which has a big conflict, including:
    
    @@ -158,10 -90,54 +159,58 @@@
                    goto leave_reset_head;
            }
      
    ++<<<<<<< HEAD
     +      if (oid != &head_oid || update_orig_head || switch_to_branch)
     +              ret = update_refs(opts, oid);
    ++=======
    + reset_head_refs:
    +       reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);
    +       strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : default_reflog_action);

I.e. the oid check there went away, but needs to be kept as well.

1.

diff --git a/reset.c b/reset.c
index 6441b22eebc..602cba75c1c 100644
--- a/reset.c
+++ b/reset.c
@@ -165,6 +165,8 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts)
                goto leave_reset_head;
        }
 
+       if (oid != &head_oid || update_orig_head || switch_to_branch)
+               ret = update_refs(opts, oid);
 
 leave_reset_head:
        rollback_lock_file(&lock);

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion
  2021-10-16  5:47   ` Ævar Arnfjörð Bjarmason
@ 2021-10-16 18:04     ` Junio C Hamano
  2021-10-16 18:10       ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 88+ messages in thread
From: Junio C Hamano @ 2021-10-16 18:04 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Phillip Wood, René Scharfe, Emily Shaffer, Bagas Sanjaya

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> I haven't looked into worktree-add.sh failure (and from what Eric says
> it seems unrelated), but all the rest is due to a mismerge of
> reset.c. The diff here at the end on top of "seen" fixes it[1].

Ahh, of course.

The "update_refs()" call is what replaces the
big block that had a bit of conflict in the way how a hook is run,
where the original made a call to run_hook_le() now we set up an
opt structure and make a call to run_hooks_oneshot().  Dropping the
call to it would of course mean no hooks would be run from there X-<.

Thanks.

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion
  2021-10-16 18:04     ` Junio C Hamano
@ 2021-10-16 18:10       ` Ævar Arnfjörð Bjarmason
  0 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-16 18:10 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Phillip Wood, René Scharfe, Emily Shaffer, Bagas Sanjaya


On Sat, Oct 16 2021, Junio C Hamano wrote:

> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>
>> I haven't looked into worktree-add.sh failure (and from what Eric says
>> it seems unrelated), but all the rest is due to a mismerge of
>> reset.c. The diff here at the end on top of "seen" fixes it[1].
>
> Ahh, of course.
>
> The "update_refs()" call is what replaces the
> big block that had a bit of conflict in the way how a hook is run,
> where the original made a call to run_hook_le() now we set up an
> opt structure and make a call to run_hooks_oneshot().  Dropping the
> call to it would of course mean no hooks would be run from there X-<.
>
> Thanks.

I was thinking of re-rolling that series soon, hopefully with "last
nits", would you prefer to just have the reset.c part of it ejected for
now?

It's really nothing that needs to happen right now, we can do it in some
subsequent round when activity in that area has hopefully quieted.

It also means dropping the "remove old run_hook_{le,ve}() hook API", but
that can similarly come later...

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 01/13] hook: add 'run' subcommand
  2021-10-15 16:24   ` Emily Shaffer
  2021-10-15 17:53     ` Eric Sunshine
@ 2021-10-19 23:08     ` Ævar Arnfjörð Bjarmason
  1 sibling, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-19 23:08 UTC (permalink / raw)
  To: Emily Shaffer
  Cc: git, Junio C Hamano, Phillip Wood, René Scharfe, Bagas Sanjaya


On Fri, Oct 15 2021, Emily Shaffer wrote:

> On Tue, Oct 12, 2021 at 03:30:26PM +0200, Ævar Arnfjörð Bjarmason wrote:
>> 
>> 
>> In order to enable hooks to be run as an external process, by a
>> standalone Git command, or by tools which wrap Git, provide an external
>> means to run all configured hook commands for a given hook event.
>> 
>> Most of our hooks require more complex functionality than this, but
>> let's start with the bare minimum required to support our simplest
>> hooks.
>> 
>> In terms of implementation the usage_with_options() and "goto usage"
>> pattern here mirrors that of
>> builtin/{commit-graph,multi-pack-index}.c.
>> 
>> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
>> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
>> ---
>> diff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt
>> new file mode 100644
>> index 00000000000..660d6a992a0
>> --- /dev/null
>> +++ b/Documentation/git-hook.txt
>> @@ -0,0 +1,38 @@
>> +git-hook(1)
>> +===========
>> +
>> +NAME
>> +----
>> +git-hook - run git hooks
>> +
>> +SYNOPSIS
>> +--------
>> +[verse]
>> +'git hook' run <hook-name> [-- <hook-args>]
>> +
>> +DESCRIPTION
>> +-----------
>> +
>> +This command is an interface to git hooks (see linkgit:githooks[5]).
>> +Currently it only provides a convenience wrapper for running hooks for
>> +use by git itself. In the future it might gain other functionality.
>
> s/git/Git/g nit, no? I thought we were somewhat picky about capitalizing Git
> when we don't mean `git`?

I'm not sure how this got mixed up, but the text you've got quoted here
is from the v1 at:
https://lore.kernel.org/git/patch-01.13-a39c0748d3f-20211012T131934Z-avarab@gmail.com/

But you're replying to the v2 here, i.e. this, which has no such text:
https://lore.kernel.org/git/patch-v2-01.13-ba64faf0580-20211015T093918Z-avarab@gmail.com/

I see I managed to omit the In-Reply-To link from v2 to v1 by mistake,
maybe some funny mailer interaction with that?

Anyway for all the rest of the comments below & in this thread I tried
to address them in some way, re-rolling soon...

^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v3 00/13] hook.[ch]: new library to run hooks + simple hook conversion
  2021-10-15  9:43 [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                   ` (14 preceding siblings ...)
  2021-10-16  0:58 ` Junio C Hamano
@ 2021-10-19 23:20 ` Ævar Arnfjörð Bjarmason
  2021-10-19 23:20   ` [PATCH v3 01/13] hook: add 'run' subcommand Ævar Arnfjörð Bjarmason
                     ` (14 more replies)
  15 siblings, 15 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-19 23:20 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

Part 2 of the greater configurable hook saga, starting by converting
some existing simple hooks to the new hook.[ch] library and "git hook
run" utility.

For more context:

See v2: https://lore.kernel.org/git/cover-v2-00.13-00000000000-20211015T093918Z-avarab@gmail.com
and v1: https://lore.kernel.org/git/cover-00.13-00000000000-20211012T131934Z-avarab@gmail.com/

Changes since v2:

 * I got some of the s/struct hook/const char *hook_path/ conversion
   wrong in v1->v2, which is the cause of the bug in "seen" with
   t2400*.sh, this fixes that.

 * The commit message for the git-p4 change was also out of date (made
   sense before some previous re-ordering). I also updated a comment
   there.

 * Rebased on "master" for getting past a couple of small
   conflicts. (seen in the range-diff).

 * An include was happening in the wrong commit (was used later in the
   series), moved.

Emily Shaffer (12):
  hook: add 'run' subcommand
  gc: use hook library for pre-auto-gc hook
  rebase: convert pre-rebase to use hook.h
  am: convert applypatch to use hook.h
  hooks: convert 'post-checkout' hook to hook library
  merge: convert post-merge to use hook.h
  send-email: use 'git hook run' for 'sendemail-validate'
  git-p4: use 'git hook' to run hooks
  commit: convert {pre-commit,prepare-commit-msg} hook to hook.h
  read-cache: convert post-index-change to use hook.h
  receive-pack: convert push-to-checkout hook to hook.h
  run-command: remove old run_hook_{le,ve}() hook API

Ævar Arnfjörð Bjarmason (1):
  git hook run: add an --ignore-missing flag

 .gitignore                 |   1 +
 Documentation/git-hook.txt |  45 +++++++++++++
 Documentation/githooks.txt |   4 ++
 Makefile                   |   1 +
 builtin.h                  |   1 +
 builtin/am.c               |   8 ++-
 builtin/checkout.c         |  14 ++--
 builtin/clone.c            |   7 +-
 builtin/gc.c               |   3 +-
 builtin/hook.c             |  90 +++++++++++++++++++++++++
 builtin/merge.c            |   4 +-
 builtin/rebase.c           |   8 ++-
 builtin/receive-pack.c     |   7 +-
 builtin/worktree.c         |  28 ++++----
 command-list.txt           |   1 +
 commit.c                   |  15 +++--
 git-p4.py                  |  70 ++-----------------
 git-send-email.perl        |  22 +++---
 git.c                      |   1 +
 hook.c                     | 121 +++++++++++++++++++++++++++++++++
 hook.h                     |  56 ++++++++++++++++
 read-cache.c               |  11 ++-
 reset.c                    |  14 ++--
 run-command.c              |  32 ---------
 run-command.h              |  17 -----
 t/t1800-hook.sh            | 134 +++++++++++++++++++++++++++++++++++++
 t/t9001-send-email.sh      |   4 +-
 27 files changed, 550 insertions(+), 169 deletions(-)
 create mode 100644 Documentation/git-hook.txt
 create mode 100644 builtin/hook.c
 create mode 100755 t/t1800-hook.sh

Range-diff against v2:
 1:  ba64faf0580 =  1:  02fd699e699 hook: add 'run' subcommand
 2:  e3dc0aed81b =  2:  42cc4d2c3c6 gc: use hook library for pre-auto-gc hook
 3:  6227a1e644d !  3:  cbbfd77a4f6 rebase: convert pre-rebase to use hook.h
    @@ builtin/rebase.c
      #define DEFAULT_REFLOG_ACTION "rebase"
      
     @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)
    - 	char *squash_onto_name = NULL;
      	int reschedule_failed_exec = -1;
      	int allow_preemptive_ff = 1;
    + 	int preserve_merges_selected = 0;
     +	struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;
      	struct option builtin_rebase_options[] = {
      		OPT_STRING(0, "onto", &options.onto_name,
 4:  0e34eb54054 =  4:  b26cef24f39 am: convert applypatch to use hook.h
 5:  a4df96c1719 !  5:  2a747a65829 hooks: convert 'post-checkout' hook to hook library
    @@ hook.c: static int notify_hook_finished(int result,
      	struct hook_cb_data cb_data = {
      		.rc = 0,
      		.hook_name = hook_name,
    +-		.hook_path = hook_path,
    + 		.options = options,
    + 	};
    + 	int jobs = 1;
     @@ hook.c: int run_hooks(const char *hook_name, const char *hook_path,
      	if (!options)
      		BUG("a struct run_hooks_opt must be provided to run_hooks");
    @@ hook.c: int run_hooks(const char *hook_name, const char *hook_path,
     +		strbuf_add_absolute_path(&abs_path, hook_path);
     +		hook_path = abs_path.buf;
     +	}
    ++	cb_data.hook_path = hook_path;
     +
      	run_processes_parallel_tr2(jobs,
      				   pick_next_hook,
    @@ hook.h: struct run_hooks_opt
      
      #define RUN_HOOKS_OPT_INIT { \
     
    - ## read-cache.c ##
    -@@
    - #include "sparse-index.h"
    - #include "csum-file.h"
    - #include "promisor-remote.h"
    -+#include "hook.h"
    - 
    - /* Mask for the name length in ce_flags in the on-disk index */
    - 
    -
      ## reset.c ##
     @@
      #include "tree-walk.h"
 6:  327f916f8c3 =  6:  7a9fd8627cd merge: convert post-merge to use hook.h
 7:  328767015b1 =  7:  840fb530df3 git hook run: add an --ignore-missing flag
 8:  6c4ebd68d56 =  8:  716ebabd794 send-email: use 'git hook run' for 'sendemail-validate'
 9:  b1f52733e3c !  9:  95782109270 git-p4: use 'git hook' to run hooks
    @@ Commit message
         Python, we can directly call 'git hook run'. We emulate the existence
         check with the --ignore-missing flag.
     
    -    As this is the last hook execution in git.git to not go through "git
    -    hook run" or the hook.[ch] library we can now be absolutely sure that
    -    our assertion in hook.c that only hooks known by the generated (from
    -    githooks(5)) hook-list.h are permitted.
    +    We're dropping the "verbose" handling added in 9f59ca4d6af (git-p4:
    +    create new function run_git_hook, 2020-02-11), those who want
    +    diagnostic output about how hooks are run are now able to get that via
    +    e.g. the trace2 facility and GIT_TRACE=1.
     
         Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
      ## git-p4.py ##
     @@ git-p4.py: def decode_path(path):
    -         return path
      
      def run_git_hook(cmd, param=[]):
    --    """Execute a hook if the hook exists."""
    +     """Execute a hook if the hook exists."""
     -    if verbose:
     -        sys.stderr.write("Looking for hook: %s\n" % cmd)
     -        sys.stderr.flush()
    @@ git-p4.py: def decode_path(path):
     -            use_shell = True
     -    return subprocess.call(cli, shell=use_shell)
     -
    -+    """args are specified with -a <arg> -a <arg> -a <arg>"""
     +    args = ['git', 'hook', 'run', '--ignore-missing', cmd]
     +    if param:
     +        args.append("--")
10:  dc31d98acdf = 10:  706426c8a79 commit: convert {pre-commit,prepare-commit-msg} hook to hook.h
11:  58b7689e4af ! 11:  39069a9c3ff read-cache: convert post-index-change to use hook.h
    @@ Commit message
         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
      ## read-cache.c ##
    +@@
    + #include "sparse-index.h"
    + #include "csum-file.h"
    + #include "promisor-remote.h"
    ++#include "hook.h"
    + 
    + /* Mask for the name length in ce_flags in the on-disk index */
    + 
     @@ read-cache.c: static int do_write_locked_index(struct index_state *istate, struct lock_file *l
      {
      	int ret;
12:  ae1e2a82147 = 12:  9818078f1e5 receive-pack: convert push-to-checkout hook to hook.h
13:  289d5a2d849 = 13:  1bc080d3611 run-command: remove old run_hook_{le,ve}() hook API
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v3 01/13] hook: add 'run' subcommand
  2021-10-19 23:20 ` [PATCH v3 " Ævar Arnfjörð Bjarmason
@ 2021-10-19 23:20   ` Ævar Arnfjörð Bjarmason
  2021-10-19 23:20   ` [PATCH v3 02/13] gc: use hook library for pre-auto-gc hook Ævar Arnfjörð Bjarmason
                     ` (13 subsequent siblings)
  14 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-19 23:20 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

In order to enable hooks to be run as an external process, by a
standalone Git command, or by tools which wrap Git, provide an external
means to run all configured hook commands for a given hook event.

Most of our hooks require more complex functionality than this, but
let's start with the bare minimum required to support our simplest
hooks.

In terms of implementation the usage_with_options() and "goto usage"
pattern here mirrors that of
builtin/{commit-graph,multi-pack-index}.c.

Some of the implementation here, such as a function being named
run_hooks() when it's tasked with running one hook, to using the
run_processes_parallel_tr2() API to run with jobs=1 is somewhere
between a bit odd and and an overkill for the current features of this
"hook run" command and the hook.[ch] API.

This code will eventually be able to run multiple hooks declared in
config in parallel, by starting out with these names and APIs we
reduce the later churn of renaming functions, switching from the
run_command() to run_processes_parallel_tr2() API etc.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 .gitignore                 |   1 +
 Documentation/git-hook.txt |  37 +++++++++++
 Documentation/githooks.txt |   4 ++
 Makefile                   |   1 +
 builtin.h                  |   1 +
 builtin/hook.c             |  85 ++++++++++++++++++++++++
 command-list.txt           |   1 +
 git.c                      |   1 +
 hook.c                     |  91 ++++++++++++++++++++++++++
 hook.h                     |  34 ++++++++++
 t/t1800-hook.sh            | 129 +++++++++++++++++++++++++++++++++++++
 11 files changed, 385 insertions(+)
 create mode 100644 Documentation/git-hook.txt
 create mode 100644 builtin/hook.c
 create mode 100755 t/t1800-hook.sh

diff --git a/.gitignore b/.gitignore
index 054249b20a8..f817c509ec0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -77,6 +77,7 @@
 /git-grep
 /git-hash-object
 /git-help
+/git-hook
 /git-http-backend
 /git-http-fetch
 /git-http-push
diff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt
new file mode 100644
index 00000000000..e39b1b5d069
--- /dev/null
+++ b/Documentation/git-hook.txt
@@ -0,0 +1,37 @@
+git-hook(1)
+===========
+
+NAME
+----
+git-hook - Run git hooks
+
+SYNOPSIS
+--------
+[verse]
+'git hook' run <hook-name> [-- <hook-args>]
+
+DESCRIPTION
+-----------
+
+A command interface to running git hooks (see linkgit:githooks[5]),
+for use by other scripted git commands.
+
+SUBCOMMANDS
+-----------
+
+run::
+	Run the `<hook-name>` hook. See linkgit:githooks[5] for
+	supported hook names.
++
+
+Any positional arguments to the hook should be passed after a
+mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See
+linkgit:githooks[5] for arguments hooks might expect (if any).
+
+SEE ALSO
+--------
+linkgit:githooks[5]
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index b51959ff941..a16e62bc8c8 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -698,6 +698,10 @@ and "0" meaning they were not.
 Only one parameter should be set to "1" when the hook runs.  The hook
 running passing "1", "1" should not be possible.
 
+SEE ALSO
+--------
+linkgit:git-hook[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/Makefile b/Makefile
index 381bed2c1d2..1eef009def2 100644
--- a/Makefile
+++ b/Makefile
@@ -1107,6 +1107,7 @@ BUILTIN_OBJS += builtin/get-tar-commit-id.o
 BUILTIN_OBJS += builtin/grep.o
 BUILTIN_OBJS += builtin/hash-object.o
 BUILTIN_OBJS += builtin/help.o
+BUILTIN_OBJS += builtin/hook.o
 BUILTIN_OBJS += builtin/index-pack.o
 BUILTIN_OBJS += builtin/init-db.o
 BUILTIN_OBJS += builtin/interpret-trailers.o
diff --git a/builtin.h b/builtin.h
index 8a58743ed63..83379f3832c 100644
--- a/builtin.h
+++ b/builtin.h
@@ -164,6 +164,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);
 int cmd_grep(int argc, const char **argv, const char *prefix);
 int cmd_hash_object(int argc, const char **argv, const char *prefix);
 int cmd_help(int argc, const char **argv, const char *prefix);
+int cmd_hook(int argc, const char **argv, const char *prefix);
 int cmd_index_pack(int argc, const char **argv, const char *prefix);
 int cmd_init_db(int argc, const char **argv, const char *prefix);
 int cmd_interpret_trailers(int argc, const char **argv, const char *prefix);
diff --git a/builtin/hook.c b/builtin/hook.c
new file mode 100644
index 00000000000..41dd15550cf
--- /dev/null
+++ b/builtin/hook.c
@@ -0,0 +1,85 @@
+#include "cache.h"
+#include "builtin.h"
+#include "config.h"
+#include "hook.h"
+#include "parse-options.h"
+#include "strbuf.h"
+#include "strvec.h"
+
+#define BUILTIN_HOOK_RUN_USAGE \
+	N_("git hook run <hook-name> [-- <hook-args>]")
+
+static const char * const builtin_hook_usage[] = {
+	BUILTIN_HOOK_RUN_USAGE,
+	NULL
+};
+
+static const char * const builtin_hook_run_usage[] = {
+	BUILTIN_HOOK_RUN_USAGE,
+	NULL
+};
+
+static int run(int argc, const char **argv, const char *prefix)
+{
+	int i;
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+	const char *hook_name;
+	const char *hook_path;
+	struct option run_options[] = {
+		OPT_END(),
+	};
+	int ret;
+
+	argc = parse_options(argc, argv, prefix, run_options,
+			     builtin_hook_run_usage,
+			     PARSE_OPT_KEEP_DASHDASH);
+
+	if (!argc)
+		goto usage;
+
+	/*
+	 * Having a -- for "run" when providing <hook-args> is
+	 * mandatory.
+	 */
+	if (argc > 1 && strcmp(argv[1], "--") &&
+	    strcmp(argv[1], "--end-of-options"))
+		goto usage;
+
+	/* Add our arguments, start after -- */
+	for (i = 2 ; i < argc; i++)
+		strvec_push(&opt.args, argv[i]);
+
+	/* Need to take into account core.hooksPath */
+	git_config(git_default_config, NULL);
+
+	hook_name = argv[0];
+	hook_path = find_hook(hook_name);
+	if (!hook_path) {
+		error("cannot find a hook named %s", hook_name);
+		return 1;
+	}
+
+	ret = run_hooks(hook_name, hook_path, &opt);
+	run_hooks_opt_clear(&opt);
+	return ret;
+usage:
+	usage_with_options(builtin_hook_run_usage, run_options);
+}
+
+int cmd_hook(int argc, const char **argv, const char *prefix)
+{
+	struct option builtin_hook_options[] = {
+		OPT_END(),
+	};
+
+	argc = parse_options(argc, argv, NULL, builtin_hook_options,
+			     builtin_hook_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+	if (!argc)
+		goto usage;
+
+	if (!strcmp(argv[0], "run"))
+		return run(argc, argv, prefix);
+
+usage:
+	usage_with_options(builtin_hook_usage, builtin_hook_options);
+}
diff --git a/command-list.txt b/command-list.txt
index a289f09ed6f..17c3958802e 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -103,6 +103,7 @@ git-grep                                mainporcelain           info
 git-gui                                 mainporcelain
 git-hash-object                         plumbingmanipulators
 git-help                                ancillaryinterrogators          complete
+git-hook                                purehelpers
 git-http-backend                        synchingrepositories
 git-http-fetch                          synchelpers
 git-http-push                           synchelpers
diff --git a/git.c b/git.c
index 5ff21be21f3..a5be02b04b8 100644
--- a/git.c
+++ b/git.c
@@ -538,6 +538,7 @@ static struct cmd_struct commands[] = {
 	{ "grep", cmd_grep, RUN_SETUP_GENTLY },
 	{ "hash-object", cmd_hash_object },
 	{ "help", cmd_help },
+	{ "hook", cmd_hook, RUN_SETUP },
 	{ "index-pack", cmd_index_pack, RUN_SETUP_GENTLY | NO_PARSEOPT },
 	{ "init", cmd_init_db },
 	{ "init-db", cmd_init_db },
diff --git a/hook.c b/hook.c
index 55e1145a4b7..20a1a4fec33 100644
--- a/hook.c
+++ b/hook.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "hook.h"
 #include "run-command.h"
+#include "config.h"
 
 const char *find_hook(const char *name)
 {
@@ -40,3 +41,93 @@ int hook_exists(const char *name)
 {
 	return !!find_hook(name);
 }
+
+void run_hooks_opt_clear(struct run_hooks_opt *o)
+{
+	strvec_clear(&o->env);
+	strvec_clear(&o->args);
+}
+
+static int pick_next_hook(struct child_process *cp,
+			  struct strbuf *out,
+			  void *pp_cb,
+			  void **pp_task_cb)
+{
+	struct hook_cb_data *hook_cb = pp_cb;
+	const char *hook_path = hook_cb->hook_path;
+
+	if (!hook_path)
+		return 0;
+
+	cp->no_stdin = 1;
+	cp->env = hook_cb->options->env.v;
+	cp->stdout_to_stderr = 1;
+	cp->trace2_hook_name = hook_cb->hook_name;
+
+	strvec_push(&cp->args, hook_path);
+	strvec_pushv(&cp->args, hook_cb->options->args.v);
+
+	/* Provide context for errors if necessary */
+	*pp_task_cb = (char *)hook_path;
+
+	/*
+	 * This pick_next_hook() will be called again, we're only
+	 * running one hook, so indicate that no more work will be
+	 * done.
+	 */
+	hook_cb->hook_path = NULL;
+
+	return 1;
+}
+
+static int notify_start_failure(struct strbuf *out,
+				void *pp_cb,
+				void *pp_task_cp)
+{
+	struct hook_cb_data *hook_cb = pp_cb;
+	const char *hook_path = pp_task_cp;
+
+	hook_cb->rc |= 1;
+
+	strbuf_addf(out, _("Couldn't start hook '%s'\n"),
+		    hook_path);
+
+	return 1;
+}
+
+static int notify_hook_finished(int result,
+				struct strbuf *out,
+				void *pp_cb,
+				void *pp_task_cb)
+{
+	struct hook_cb_data *hook_cb = pp_cb;
+
+	hook_cb->rc |= result;
+
+	return 0;
+}
+
+int run_hooks(const char *hook_name, const char *hook_path,
+	      struct run_hooks_opt *options)
+{
+	struct hook_cb_data cb_data = {
+		.rc = 0,
+		.hook_name = hook_name,
+		.hook_path = hook_path,
+		.options = options,
+	};
+	int jobs = 1;
+
+	if (!options)
+		BUG("a struct run_hooks_opt must be provided to run_hooks");
+
+	run_processes_parallel_tr2(jobs,
+				   pick_next_hook,
+				   notify_start_failure,
+				   notify_hook_finished,
+				   &cb_data,
+				   "hook",
+				   hook_name);
+
+	return cb_data.rc;
+}
diff --git a/hook.h b/hook.h
index 6aa36fc7ff9..a9317c3f95e 100644
--- a/hook.h
+++ b/hook.h
@@ -1,5 +1,28 @@
 #ifndef HOOK_H
 #define HOOK_H
+#include "strvec.h"
+
+struct run_hooks_opt
+{
+	/* Environment vars to be set for each hook */
+	struct strvec env;
+
+	/* Args to be passed to each hook */
+	struct strvec args;
+};
+
+#define RUN_HOOKS_OPT_INIT { \
+	.env = STRVEC_INIT, \
+	.args = STRVEC_INIT, \
+}
+
+struct hook_cb_data {
+	/* rc reflects the cumulative failure state */
+	int rc;
+	const char *hook_name;
+	const char *hook_path;
+	struct run_hooks_opt *options;
+};
 
 /*
  * Returns the path to the hook file, or NULL if the hook is missing
@@ -13,4 +36,15 @@ const char *find_hook(const char *name);
  */
 int hook_exists(const char *hookname);
 
+/**
+ * Clear data from an initialized "struct run_hooks_opt".
+ */
+void run_hooks_opt_clear(struct run_hooks_opt *o);
+
+/**
+ * Takes an already resolved hook found via find_hook() and runs
+ * it. Does not call run_hooks_opt_clear() for you.
+ */
+int run_hooks(const char *hookname, const char *hook_path,
+	      struct run_hooks_opt *options);
 #endif
diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh
new file mode 100755
index 00000000000..3aea1b105f0
--- /dev/null
+++ b/t/t1800-hook.sh
@@ -0,0 +1,129 @@
+#!/bin/sh
+
+test_description='git-hook command'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+
+test_expect_success 'git hook usage' '
+	test_expect_code 129 git hook &&
+	test_expect_code 129 git hook run &&
+	test_expect_code 129 git hook run -h &&
+	test_expect_code 129 git hook run --unknown 2>err &&
+	grep "unknown option" err
+'
+
+test_expect_success 'git hook run: nonexistent hook' '
+	cat >stderr.expect <<-\EOF &&
+	error: cannot find a hook named test-hook
+	EOF
+	test_expect_code 1 git hook run test-hook 2>stderr.actual &&
+	test_cmp stderr.expect stderr.actual
+'
+
+test_expect_success 'git hook run: basic' '
+	write_script .git/hooks/test-hook <<-EOF &&
+	echo Test hook
+	EOF
+
+	cat >expect <<-\EOF &&
+	Test hook
+	EOF
+	git hook run test-hook 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git hook run: stdout and stderr both write to our stderr' '
+	write_script .git/hooks/test-hook <<-EOF &&
+	echo >&1 Will end up on stderr
+	echo >&2 Will end up on stderr
+	EOF
+
+	cat >stderr.expect <<-\EOF &&
+	Will end up on stderr
+	Will end up on stderr
+	EOF
+	git hook run test-hook >stdout.actual 2>stderr.actual &&
+	test_cmp stderr.expect stderr.actual &&
+	test_must_be_empty stdout.actual
+'
+
+test_expect_success 'git hook run: exit codes are passed along' '
+	write_script .git/hooks/test-hook <<-EOF &&
+	exit 1
+	EOF
+
+	test_expect_code 1 git hook run test-hook &&
+
+	write_script .git/hooks/test-hook <<-EOF &&
+	exit 2
+	EOF
+
+	test_expect_code 2 git hook run test-hook &&
+
+	write_script .git/hooks/test-hook <<-EOF &&
+	exit 128
+	EOF
+
+	test_expect_code 128 git hook run test-hook &&
+
+	write_script .git/hooks/test-hook <<-EOF &&
+	exit 129
+	EOF
+
+	test_expect_code 129 git hook run test-hook
+'
+
+test_expect_success 'git hook run arg u ments without -- is not allowed' '
+	test_expect_code 129 git hook run test-hook arg u ments
+'
+
+test_expect_success 'git hook run -- pass arguments' '
+	write_script .git/hooks/test-hook <<-\EOF &&
+	echo $1
+	echo $2
+	EOF
+
+	cat >expect <<-EOF &&
+	arg
+	u ments
+	EOF
+
+	git hook run test-hook -- arg "u ments" 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git hook run -- out-of-repo runs excluded' '
+	write_script .git/hooks/test-hook <<-EOF &&
+	echo Test hook
+	EOF
+
+	nongit test_must_fail git hook run test-hook
+'
+
+test_expect_success 'git -c core.hooksPath=<PATH> hook run' '
+	mkdir my-hooks &&
+	write_script my-hooks/test-hook <<-\EOF &&
+	echo Hook ran $1 >>actual
+	EOF
+
+	cat >expect <<-\EOF &&
+	Test hook
+	Hook ran one
+	Hook ran two
+	Hook ran three
+	Hook ran four
+	EOF
+
+	# Test various ways of specifying the path. See also
+	# t1350-config-hooks-path.sh
+	>actual &&
+	git hook run test-hook -- ignored 2>>actual &&
+	git -c core.hooksPath=my-hooks hook run test-hook -- one 2>>actual &&
+	git -c core.hooksPath=my-hooks/ hook run test-hook -- two 2>>actual &&
+	git -c core.hooksPath="$PWD/my-hooks" hook run test-hook -- three 2>>actual &&
+	git -c core.hooksPath="$PWD/my-hooks/" hook run test-hook -- four 2>>actual &&
+	test_cmp expect actual
+'
+
+test_done
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v3 02/13] gc: use hook library for pre-auto-gc hook
  2021-10-19 23:20 ` [PATCH v3 " Ævar Arnfjörð Bjarmason
  2021-10-19 23:20   ` [PATCH v3 01/13] hook: add 'run' subcommand Ævar Arnfjörð Bjarmason
@ 2021-10-19 23:20   ` Ævar Arnfjörð Bjarmason
  2021-10-19 23:20   ` [PATCH v3 03/13] rebase: convert pre-rebase to use hook.h Ævar Arnfjörð Bjarmason
                     ` (12 subsequent siblings)
  14 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-19 23:20 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the pre-auto-gc hook away from run-command.h to and over to the
new hook.h library.

To do this introduce a simple run_hooks_oneshot() wrapper, we'll be
using it extensively for these simple cases of wanting to run a single
hook under a given name, and having it free the memory we allocate for
us.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/gc.c |  3 ++-
 hook.c       | 20 ++++++++++++++++++++
 hook.h       | 13 +++++++++++++
 3 files changed, 35 insertions(+), 1 deletion(-)

diff --git a/builtin/gc.c b/builtin/gc.c
index 6b3de3dd514..95939e77f53 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -32,6 +32,7 @@
 #include "remote.h"
 #include "object-store.h"
 #include "exec-cmd.h"
+#include "hook.h"
 
 #define FAILED_RUN "failed to run %s"
 
@@ -394,7 +395,7 @@ static int need_to_gc(void)
 	else
 		return 0;
 
-	if (run_hook_le(NULL, "pre-auto-gc", NULL))
+	if (run_hooks_oneshot("pre-auto-gc", NULL))
 		return 0;
 	return 1;
 }
diff --git a/hook.c b/hook.c
index 20a1a4fec33..9951015dc66 100644
--- a/hook.c
+++ b/hook.c
@@ -131,3 +131,23 @@ int run_hooks(const char *hook_name, const char *hook_path,
 
 	return cb_data.rc;
 }
+
+int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options)
+{
+	const char *hook_path;
+	struct run_hooks_opt hook_opt_scratch = RUN_HOOKS_OPT_INIT;
+	int ret = 0;
+
+	if (!options)
+		options = &hook_opt_scratch;
+
+	hook_path = find_hook(hook_name);
+	if (!hook_path)
+		goto cleanup;
+
+	ret = run_hooks(hook_name, hook_path, options);
+cleanup:
+	run_hooks_opt_clear(options);
+
+	return ret;
+}
diff --git a/hook.h b/hook.h
index a9317c3f95e..9e4d10df63e 100644
--- a/hook.h
+++ b/hook.h
@@ -44,7 +44,20 @@ void run_hooks_opt_clear(struct run_hooks_opt *o);
 /**
  * Takes an already resolved hook found via find_hook() and runs
  * it. Does not call run_hooks_opt_clear() for you.
+ *
+ * See run_hooks_oneshot() for the simpler one-shot API.
  */
 int run_hooks(const char *hookname, const char *hook_path,
 	      struct run_hooks_opt *options);
+
+/**
+ * Calls find_hook() on your "hook_name" and runs the hooks (if any)
+ * with run_hooks().
+ *
+ * If "options" is provided calls run_hooks_opt_clear() on it for
+ * you. If "options" is NULL the default options from
+ * RUN_HOOKS_OPT_INIT will be used.
+ */
+int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options);
+
 #endif
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v3 03/13] rebase: convert pre-rebase to use hook.h
  2021-10-19 23:20 ` [PATCH v3 " Ævar Arnfjörð Bjarmason
  2021-10-19 23:20   ` [PATCH v3 01/13] hook: add 'run' subcommand Ævar Arnfjörð Bjarmason
  2021-10-19 23:20   ` [PATCH v3 02/13] gc: use hook library for pre-auto-gc hook Ævar Arnfjörð Bjarmason
@ 2021-10-19 23:20   ` Ævar Arnfjörð Bjarmason
  2021-10-29  5:48     ` Junio C Hamano
  2021-10-19 23:20   ` [PATCH v3 04/13] am: convert applypatch " Ævar Arnfjörð Bjarmason
                     ` (11 subsequent siblings)
  14 siblings, 1 reply; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-19 23:20 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the pre-rebase hook away from run-command.h to and over to the
new hook.h library.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/rebase.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 34b4744e5f3..758b5dfabe2 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -28,6 +28,7 @@
 #include "sequencer.h"
 #include "rebase-interactive.h"
 #include "reset.h"
+#include "hook.h"
 
 #define DEFAULT_REFLOG_ACTION "rebase"
 
@@ -1017,6 +1018,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	int reschedule_failed_exec = -1;
 	int allow_preemptive_ff = 1;
 	int preserve_merges_selected = 0;
+	struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;
 	struct option builtin_rebase_options[] = {
 		OPT_STRING(0, "onto", &options.onto_name,
 			   N_("revision"),
@@ -1711,9 +1713,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 	}
 
 	/* If a hook exists, give it a chance to interrupt*/
+	strvec_push(&hook_opt.args, options.upstream_arg);
+	if (argc)
+		strvec_push(&hook_opt.args, argv[0]);
 	if (!ok_to_skip_pre_rebase &&
-	    run_hook_le(NULL, "pre-rebase", options.upstream_arg,
-			argc ? argv[0] : NULL, NULL))
+	    run_hooks_oneshot("pre-rebase", &hook_opt))
 		die(_("The pre-rebase hook refused to rebase."));
 
 	if (options.flags & REBASE_DIFFSTAT) {
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v3 04/13] am: convert applypatch to use hook.h
  2021-10-19 23:20 ` [PATCH v3 " Ævar Arnfjörð Bjarmason
                     ` (2 preceding siblings ...)
  2021-10-19 23:20   ` [PATCH v3 03/13] rebase: convert pre-rebase to use hook.h Ævar Arnfjörð Bjarmason
@ 2021-10-19 23:20   ` Ævar Arnfjörð Bjarmason
  2021-10-19 23:20   ` [PATCH v3 05/13] hooks: convert 'post-checkout' hook to hook library Ævar Arnfjörð Bjarmason
                     ` (10 subsequent siblings)
  14 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-19 23:20 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Teach pre-applypatch, post-applypatch, and applypatch-msg to use the
hook.h library instead of the run-command.h library.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/am.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..5528f131a81 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -446,9 +446,11 @@ static void am_destroy(const struct am_state *state)
 static int run_applypatch_msg_hook(struct am_state *state)
 {
 	int ret;
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
 
 	assert(state->msg);
-	ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL);
+	strvec_push(&opt.args, am_path(state, "final-commit"));
+	ret = run_hooks_oneshot("applypatch-msg", &opt);
 
 	if (!ret) {
 		FREE_AND_NULL(state->msg);
@@ -1609,7 +1611,7 @@ static void do_commit(const struct am_state *state)
 	const char *reflog_msg, *author, *committer = NULL;
 	struct strbuf sb = STRBUF_INIT;
 
-	if (run_hook_le(NULL, "pre-applypatch", NULL))
+	if (run_hooks_oneshot("pre-applypatch", NULL))
 		exit(1);
 
 	if (write_cache_as_tree(&tree, 0, NULL))
@@ -1661,7 +1663,7 @@ static void do_commit(const struct am_state *state)
 		fclose(fp);
 	}
 
-	run_hook_le(NULL, "post-applypatch", NULL);
+	run_hooks_oneshot("post-applypatch", NULL);
 
 	strbuf_release(&sb);
 }
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v3 05/13] hooks: convert 'post-checkout' hook to hook library
  2021-10-19 23:20 ` [PATCH v3 " Ævar Arnfjörð Bjarmason
                     ` (3 preceding siblings ...)
  2021-10-19 23:20   ` [PATCH v3 04/13] am: convert applypatch " Ævar Arnfjörð Bjarmason
@ 2021-10-19 23:20   ` Ævar Arnfjörð Bjarmason
  2021-10-29  5:55     ` Junio C Hamano
  2021-10-19 23:20   ` [PATCH v3 06/13] merge: convert post-merge to use hook.h Ævar Arnfjörð Bjarmason
                     ` (9 subsequent siblings)
  14 siblings, 1 reply; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-19 23:20 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the running of the 'post-checkout' hook away from run-command.h
to the new hook.h library. For "worktree" this requires a change to it
to run the hooks from a given directory.

We could strictly speaking skip the "absolute_path" flag and just
check if "dir" is specified, but let's split them up for clarity, as
well as for any future user who'd like to set "dir" but not implicitly
change the argument to an absolute path.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/checkout.c | 14 +++++++++-----
 builtin/clone.c    |  7 +++++--
 builtin/worktree.c | 28 ++++++++++++----------------
 hook.c             | 12 +++++++++++-
 hook.h             |  9 +++++++++
 reset.c            | 14 ++++++++++----
 6 files changed, 56 insertions(+), 28 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index cbf73b8c9f6..54cbba821a8 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -9,6 +9,7 @@
 #include "config.h"
 #include "diff.h"
 #include "dir.h"
+#include "hook.h"
 #include "ll-merge.h"
 #include "lockfile.h"
 #include "merge-recursive.h"
@@ -106,13 +107,16 @@ struct branch_info {
 static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,
 			      int changed)
 {
-	return run_hook_le(NULL, "post-checkout",
-			   oid_to_hex(old_commit ? &old_commit->object.oid : null_oid()),
-			   oid_to_hex(new_commit ? &new_commit->object.oid : null_oid()),
-			   changed ? "1" : "0", NULL);
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+
 	/* "new_commit" can be NULL when checking out from the index before
 	   a commit exists. */
-
+	strvec_pushl(&opt.args,
+		     oid_to_hex(old_commit ? &old_commit->object.oid : null_oid()),
+		     oid_to_hex(new_commit ? &new_commit->object.oid : null_oid()),
+		     changed ? "1" : "0",
+		     NULL);
+	return run_hooks_oneshot("post-checkout", &opt);
 }
 
 static int update_some(const struct object_id *oid, struct strbuf *base,
diff --git a/builtin/clone.c b/builtin/clone.c
index 559acf9e036..53ca5ca0eb2 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -32,6 +32,7 @@
 #include "connected.h"
 #include "packfile.h"
 #include "list-objects-filter-options.h"
+#include "hook.h"
 
 /*
  * Overall FIXMEs:
@@ -659,6 +660,7 @@ static int checkout(int submodule_progress)
 	struct tree *tree;
 	struct tree_desc t;
 	int err = 0;
+	struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;
 
 	if (option_no_checkout)
 		return 0;
@@ -705,8 +707,9 @@ static int checkout(int submodule_progress)
 	if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
 		die(_("unable to write new index file"));
 
-	err |= run_hook_le(NULL, "post-checkout", oid_to_hex(null_oid()),
-			   oid_to_hex(&oid), "1", NULL);
+	strvec_pushl(&hook_opt.args, oid_to_hex(null_oid()), oid_to_hex(&oid),
+		     "1", NULL);
+	err |= run_hooks_oneshot("post-checkout", &hook_opt);
 
 	if (!err && (option_recurse_submodules.nr > 0)) {
 		struct strvec args = STRVEC_INIT;
diff --git a/builtin/worktree.c b/builtin/worktree.c
index d22ece93e1a..330867c19bf 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -382,22 +382,18 @@ static int add_worktree(const char *path, const char *refname,
 	 * is_junk is cleared, but do return appropriate code when hook fails.
 	 */
 	if (!ret && opts->checkout) {
-		const char *hook = find_hook("post-checkout");
-		if (hook) {
-			const char *env[] = { "GIT_DIR", "GIT_WORK_TREE", NULL };
-			cp.git_cmd = 0;
-			cp.no_stdin = 1;
-			cp.stdout_to_stderr = 1;
-			cp.dir = path;
-			cp.env = env;
-			cp.argv = NULL;
-			cp.trace2_hook_name = "post-checkout";
-			strvec_pushl(&cp.args, absolute_path(hook),
-				     oid_to_hex(null_oid()),
-				     oid_to_hex(&commit->object.oid),
-				     "1", NULL);
-			ret = run_command(&cp);
-		}
+		struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+
+		strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL);
+		strvec_pushl(&opt.args,
+			     oid_to_hex(null_oid()),
+			     oid_to_hex(&commit->object.oid),
+			     "1",
+			     NULL);
+		opt.dir = path;
+		opt.absolute_path = 1;
+
+		ret = run_hooks_oneshot("post-checkout", &opt);
 	}
 
 	strvec_clear(&child_env);
diff --git a/hook.c b/hook.c
index 9951015dc66..bf34cd0a2c1 100644
--- a/hook.c
+++ b/hook.c
@@ -63,6 +63,7 @@ static int pick_next_hook(struct child_process *cp,
 	cp->env = hook_cb->options->env.v;
 	cp->stdout_to_stderr = 1;
 	cp->trace2_hook_name = hook_cb->hook_name;
+	cp->dir = hook_cb->options->dir;
 
 	strvec_push(&cp->args, hook_path);
 	strvec_pushv(&cp->args, hook_cb->options->args.v);
@@ -110,10 +111,10 @@ static int notify_hook_finished(int result,
 int run_hooks(const char *hook_name, const char *hook_path,
 	      struct run_hooks_opt *options)
 {
+	struct strbuf abs_path = STRBUF_INIT;
 	struct hook_cb_data cb_data = {
 		.rc = 0,
 		.hook_name = hook_name,
-		.hook_path = hook_path,
 		.options = options,
 	};
 	int jobs = 1;
@@ -121,6 +122,12 @@ int run_hooks(const char *hook_name, const char *hook_path,
 	if (!options)
 		BUG("a struct run_hooks_opt must be provided to run_hooks");
 
+	if (options->absolute_path) {
+		strbuf_add_absolute_path(&abs_path, hook_path);
+		hook_path = abs_path.buf;
+	}
+	cb_data.hook_path = hook_path;
+
 	run_processes_parallel_tr2(jobs,
 				   pick_next_hook,
 				   notify_start_failure,
@@ -129,6 +136,9 @@ int run_hooks(const char *hook_name, const char *hook_path,
 				   "hook",
 				   hook_name);
 
+	if (options->absolute_path)
+		strbuf_release(&abs_path);
+
 	return cb_data.rc;
 }
 
diff --git a/hook.h b/hook.h
index 9e4d10df63e..252a605b125 100644
--- a/hook.h
+++ b/hook.h
@@ -9,6 +9,15 @@ struct run_hooks_opt
 
 	/* Args to be passed to each hook */
 	struct strvec args;
+
+	/*
+	 * Resolve and run the "absolute_path(hook)" instead of
+	 * "hook". Used for "git worktree" hooks
+	 */
+	int absolute_path;
+
+	/* Path to initial working directory for subprocess */
+	const char *dir;
 };
 
 #define RUN_HOOKS_OPT_INIT { \
diff --git a/reset.c b/reset.c
index f214df3d96c..4234dd345db 100644
--- a/reset.c
+++ b/reset.c
@@ -7,6 +7,7 @@
 #include "tree-walk.h"
 #include "tree.h"
 #include "unpack-trees.h"
+#include "hook.h"
 
 int reset_head(struct repository *r, struct object_id *oid, const char *action,
 	       const char *switch_to_branch, unsigned flags,
@@ -126,10 +127,15 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,
 			ret = create_symref("HEAD", switch_to_branch,
 					    reflog_head);
 	}
-	if (run_hook)
-		run_hook_le(NULL, "post-checkout",
-			    oid_to_hex(orig ? orig : null_oid()),
-			    oid_to_hex(oid), "1", NULL);
+	if (run_hook) {
+		struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+		strvec_pushl(&opt.args,
+			     oid_to_hex(orig ? orig : null_oid()),
+			     oid_to_hex(oid),
+			     "1",
+			     NULL);
+		run_hooks_oneshot("post-checkout", &opt);
+	}
 
 leave_reset_head:
 	strbuf_release(&msg);
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v3 06/13] merge: convert post-merge to use hook.h
  2021-10-19 23:20 ` [PATCH v3 " Ævar Arnfjörð Bjarmason
                     ` (4 preceding siblings ...)
  2021-10-19 23:20   ` [PATCH v3 05/13] hooks: convert 'post-checkout' hook to hook library Ævar Arnfjörð Bjarmason
@ 2021-10-19 23:20   ` Ævar Arnfjörð Bjarmason
  2021-10-19 23:20   ` [PATCH v3 07/13] git hook run: add an --ignore-missing flag Ævar Arnfjörð Bjarmason
                     ` (8 subsequent siblings)
  14 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-19 23:20 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Teach post-merge to use the hook.h library instead of the
run-command.h library to run hooks.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/merge.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/builtin/merge.c b/builtin/merge.c
index cc4a910c69b..c48b875b82b 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -448,6 +448,7 @@ static void finish(struct commit *head_commit,
 		   const struct object_id *new_head, const char *msg)
 {
 	struct strbuf reflog_message = STRBUF_INIT;
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
 	const struct object_id *head = &head_commit->object.oid;
 
 	if (!msg)
@@ -488,7 +489,8 @@ static void finish(struct commit *head_commit,
 	}
 
 	/* Run a post-merge hook */
-	run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL);
+	strvec_push(&opt.args, squash ? "1" : "0");
+	run_hooks_oneshot("post-merge", &opt);
 
 	apply_autostash(git_path_merge_autostash(the_repository));
 	strbuf_release(&reflog_message);
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v3 07/13] git hook run: add an --ignore-missing flag
  2021-10-19 23:20 ` [PATCH v3 " Ævar Arnfjörð Bjarmason
                     ` (5 preceding siblings ...)
  2021-10-19 23:20   ` [PATCH v3 06/13] merge: convert post-merge to use hook.h Ævar Arnfjörð Bjarmason
@ 2021-10-19 23:20   ` Ævar Arnfjörð Bjarmason
  2021-10-29  5:59     ` Junio C Hamano
  2021-10-19 23:20   ` [PATCH v3 08/13] send-email: use 'git hook run' for 'sendemail-validate' Ævar Arnfjörð Bjarmason
                     ` (7 subsequent siblings)
  14 siblings, 1 reply; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-19 23:20 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

For certain one-shot hooks we'd like to optimistically run them, and
not complain if they don't exist. This will be used by send-email in a
subsequent commit.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 Documentation/git-hook.txt | 10 +++++++++-
 builtin/hook.c             |  7 ++++++-
 t/t1800-hook.sh            |  5 +++++
 3 files changed, 20 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt
index e39b1b5d069..77c3a8ad909 100644
--- a/Documentation/git-hook.txt
+++ b/Documentation/git-hook.txt
@@ -8,7 +8,7 @@ git-hook - Run git hooks
 SYNOPSIS
 --------
 [verse]
-'git hook' run <hook-name> [-- <hook-args>]
+'git hook' run [--ignore-missing] <hook-name> [-- <hook-args>]
 
 DESCRIPTION
 -----------
@@ -28,6 +28,14 @@ Any positional arguments to the hook should be passed after a
 mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See
 linkgit:githooks[5] for arguments hooks might expect (if any).
 
+OPTIONS
+-------
+
+--ignore-missing::
+	Ignore any missing hook by quietly returning zero. Used for
+	tools that want to do a blind one-shot run of a hook that may
+	or may not be present.
+
 SEE ALSO
 --------
 linkgit:githooks[5]
diff --git a/builtin/hook.c b/builtin/hook.c
index 41dd15550cf..fa26454990d 100644
--- a/builtin/hook.c
+++ b/builtin/hook.c
@@ -7,7 +7,7 @@
 #include "strvec.h"
 
 #define BUILTIN_HOOK_RUN_USAGE \
-	N_("git hook run <hook-name> [-- <hook-args>]")
+	N_("git hook run [--ignore-missing] <hook-name> [-- <hook-args>]")
 
 static const char * const builtin_hook_usage[] = {
 	BUILTIN_HOOK_RUN_USAGE,
@@ -23,9 +23,12 @@ static int run(int argc, const char **argv, const char *prefix)
 {
 	int i;
 	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+	int ignore_missing = 0;
 	const char *hook_name;
 	const char *hook_path;
 	struct option run_options[] = {
+		OPT_BOOL(0, "ignore-missing", &ignore_missing,
+			 N_("silently ignore missing requested <hook-name>")),
 		OPT_END(),
 	};
 	int ret;
@@ -53,6 +56,8 @@ static int run(int argc, const char **argv, const char *prefix)
 	git_config(git_default_config, NULL);
 
 	hook_name = argv[0];
+	if (ignore_missing)
+		return run_hooks_oneshot(hook_name, &opt);
 	hook_path = find_hook(hook_name);
 	if (!hook_path) {
 		error("cannot find a hook named %s", hook_name);
diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh
index 3aea1b105f0..29718aa9913 100755
--- a/t/t1800-hook.sh
+++ b/t/t1800-hook.sh
@@ -21,6 +21,11 @@ test_expect_success 'git hook run: nonexistent hook' '
 	test_cmp stderr.expect stderr.actual
 '
 
+test_expect_success 'git hook run: nonexistent hook with --ignore-missing' '
+	git hook run --ignore-missing does-not-exist 2>stderr.actual &&
+	test_must_be_empty stderr.actual
+'
+
 test_expect_success 'git hook run: basic' '
 	write_script .git/hooks/test-hook <<-EOF &&
 	echo Test hook
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v3 08/13] send-email: use 'git hook run' for 'sendemail-validate'
  2021-10-19 23:20 ` [PATCH v3 " Ævar Arnfjörð Bjarmason
                     ` (6 preceding siblings ...)
  2021-10-19 23:20   ` [PATCH v3 07/13] git hook run: add an --ignore-missing flag Ævar Arnfjörð Bjarmason
@ 2021-10-19 23:20   ` Ævar Arnfjörð Bjarmason
  2021-10-19 23:20   ` [PATCH v3 09/13] git-p4: use 'git hook' to run hooks Ævar Arnfjörð Bjarmason
                     ` (6 subsequent siblings)
  14 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-19 23:20 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Change the "sendmail-validate" hook to be run via the "git hook run"
wrapper instead of via a direct invocation.

This is the smallest possibly change to get "send-email" using "git
hook run". We still check the hook itself with "-x", and set a
"GIT_DIR" variable, both of which are asserted by our tests. We'll
need to get rid of this special behavior if we start running N hooks,
but for now let's be as close to bug-for-bug compatible as possible.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 git-send-email.perl   | 22 ++++++++++++++--------
 t/t9001-send-email.sh |  4 ++--
 2 files changed, 16 insertions(+), 10 deletions(-)

diff --git a/git-send-email.perl b/git-send-email.perl
index 5262d88ee32..4c20c0bbb79 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -195,13 +195,13 @@ sub format_2822_time {
 my $editor;
 
 sub system_or_msg {
-	my ($args, $msg) = @_;
+	my ($args, $msg, $cmd_name) = @_;
 	system(@$args);
 	my $signalled = $? & 127;
 	my $exit_code = $? >> 8;
 	return unless $signalled or $exit_code;
 
-	my @sprintf_args = ($args->[0], $exit_code);
+	my @sprintf_args = ($cmd_name ? $cmd_name : $args->[0], $exit_code);
 	if (defined $msg) {
 		# Quiet the 'redundant' warning category, except we
 		# need to support down to Perl 5.8, so we can't do a
@@ -2039,10 +2039,10 @@ sub validate_patch {
 	my ($fn, $xfer_encoding) = @_;
 
 	if ($repo) {
+		my $hook_name = 'sendemail-validate';
 		my $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks');
 		require File::Spec;
-		my $validate_hook = File::Spec->catfile($hooks_path,
-					    'sendemail-validate');
+		my $validate_hook = File::Spec->catfile($hooks_path, $hook_name);
 		my $hook_error;
 		if (-x $validate_hook) {
 			require Cwd;
@@ -2052,13 +2052,19 @@ sub validate_patch {
 			chdir($repo->wc_path() or $repo->repo_path())
 				or die("chdir: $!");
 			local $ENV{"GIT_DIR"} = $repo->repo_path();
-			$hook_error = system_or_msg([$validate_hook, $target]);
+			my @cmd = ("git", "hook", "run", "--ignore-missing",
+				    $hook_name, "--");
+			my @cmd_msg = (@cmd, "<patch>");
+			my @cmd_run = (@cmd, $target);
+			$hook_error = system_or_msg(\@cmd_run, undef, "@cmd_msg");
 			chdir($cwd_save) or die("chdir: $!");
 		}
 		if ($hook_error) {
-			die sprintf(__("fatal: %s: rejected by sendemail-validate hook\n" .
-				       "%s\n" .
-				       "warning: no patches were sent\n"), $fn, $hook_error);
+			$hook_error = sprintf(__("fatal: %s: rejected by %s hook\n" .
+						 $hook_error . "\n" .
+						 "warning: no patches were sent\n"),
+					      $fn, $hook_name);
+			die $hook_error;
 		}
 	}
 
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index aa0c20499ba..84d0f40d76a 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -539,7 +539,7 @@ test_expect_success $PREREQ "--validate respects relative core.hooksPath path" '
 	test_path_is_file my-hooks.ran &&
 	cat >expect <<-EOF &&
 	fatal: longline.patch: rejected by sendemail-validate hook
-	fatal: command '"'"'my-hooks/sendemail-validate'"'"' died with exit code 1
+	fatal: command '"'"'git hook run --ignore-missing sendemail-validate -- <patch>'"'"' died with exit code 1
 	warning: no patches were sent
 	EOF
 	test_cmp expect actual
@@ -558,7 +558,7 @@ test_expect_success $PREREQ "--validate respects absolute core.hooksPath path" '
 	test_path_is_file my-hooks.ran &&
 	cat >expect <<-EOF &&
 	fatal: longline.patch: rejected by sendemail-validate hook
-	fatal: command '"'"'$hooks_path/sendemail-validate'"'"' died with exit code 1
+	fatal: command '"'"'git hook run --ignore-missing sendemail-validate -- <patch>'"'"' died with exit code 1
 	warning: no patches were sent
 	EOF
 	test_cmp expect actual
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v3 09/13] git-p4: use 'git hook' to run hooks
  2021-10-19 23:20 ` [PATCH v3 " Ævar Arnfjörð Bjarmason
                     ` (7 preceding siblings ...)
  2021-10-19 23:20   ` [PATCH v3 08/13] send-email: use 'git hook run' for 'sendemail-validate' Ævar Arnfjörð Bjarmason
@ 2021-10-19 23:20   ` Ævar Arnfjörð Bjarmason
  2021-10-19 23:20   ` [PATCH v3 10/13] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h Ævar Arnfjörð Bjarmason
                     ` (5 subsequent siblings)
  14 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-19 23:20 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Instead of duplicating the behavior of run-command.h:run_hook_le() in
Python, we can directly call 'git hook run'. We emulate the existence
check with the --ignore-missing flag.

We're dropping the "verbose" handling added in 9f59ca4d6af (git-p4:
create new function run_git_hook, 2020-02-11), those who want
diagnostic output about how hooks are run are now able to get that via
e.g. the trace2 facility and GIT_TRACE=1.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 git-p4.py | 70 +++++--------------------------------------------------
 1 file changed, 6 insertions(+), 64 deletions(-)

diff --git a/git-p4.py b/git-p4.py
index 2b4500226aa..3b54168eb4a 100755
--- a/git-p4.py
+++ b/git-p4.py
@@ -208,70 +208,12 @@ def decode_path(path):
 
 def run_git_hook(cmd, param=[]):
     """Execute a hook if the hook exists."""
-    if verbose:
-        sys.stderr.write("Looking for hook: %s\n" % cmd)
-        sys.stderr.flush()
-
-    hooks_path = gitConfig("core.hooksPath")
-    if len(hooks_path) <= 0:
-        hooks_path = os.path.join(os.environ["GIT_DIR"], "hooks")
-
-    if not isinstance(param, list):
-        param=[param]
-
-    # resolve hook file name, OS depdenent
-    hook_file = os.path.join(hooks_path, cmd)
-    if platform.system() == 'Windows':
-        if not os.path.isfile(hook_file):
-            # look for the file with an extension
-            files = glob.glob(hook_file + ".*")
-            if not files:
-                return True
-            files.sort()
-            hook_file = files.pop()
-            while hook_file.upper().endswith(".SAMPLE"):
-                # The file is a sample hook. We don't want it
-                if len(files) > 0:
-                    hook_file = files.pop()
-                else:
-                    return True
-
-    if not os.path.isfile(hook_file) or not os.access(hook_file, os.X_OK):
-        return True
-
-    return run_hook_command(hook_file, param) == 0
-
-def run_hook_command(cmd, param):
-    """Executes a git hook command
-       cmd = the command line file to be executed. This can be
-       a file that is run by OS association.
-
-       param = a list of parameters to pass to the cmd command
-
-       On windows, the extension is checked to see if it should
-       be run with the Git for Windows Bash shell.  If there
-       is no file extension, the file is deemed a bash shell
-       and will be handed off to sh.exe. Otherwise, Windows
-       will be called with the shell to handle the file assocation.
-
-       For non Windows operating systems, the file is called
-       as an executable.
-    """
-    cli = [cmd] + param
-    use_shell = False
-    if platform.system() == 'Windows':
-        (root,ext) = os.path.splitext(cmd)
-        if ext == "":
-            exe_path = os.environ.get("EXEPATH")
-            if exe_path is None:
-                exe_path = ""
-            else:
-                exe_path = os.path.join(exe_path, "bin")
-            cli = [os.path.join(exe_path, "SH.EXE")] + cli
-        else:
-            use_shell = True
-    return subprocess.call(cli, shell=use_shell)
-
+    args = ['git', 'hook', 'run', '--ignore-missing', cmd]
+    if param:
+        args.append("--")
+        for p in param:
+            args.append(p)
+    return subprocess.call(args) == 0
 
 def write_pipe(c, stdin):
     if verbose:
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v3 10/13] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h
  2021-10-19 23:20 ` [PATCH v3 " Ævar Arnfjörð Bjarmason
                     ` (8 preceding siblings ...)
  2021-10-19 23:20   ` [PATCH v3 09/13] git-p4: use 'git hook' to run hooks Ævar Arnfjörð Bjarmason
@ 2021-10-19 23:20   ` Ævar Arnfjörð Bjarmason
  2021-10-19 23:20   ` [PATCH v3 11/13] read-cache: convert post-index-change to use hook.h Ævar Arnfjörð Bjarmason
                     ` (4 subsequent siblings)
  14 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-19 23:20 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move these hooks hook away from run-command.h to and over to the new
hook.h library.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 commit.c | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/commit.c b/commit.c
index 551de4903c1..4e7cbd7585d 100644
--- a/commit.c
+++ b/commit.c
@@ -21,6 +21,7 @@
 #include "commit-reach.h"
 #include "run-command.h"
 #include "shallow.h"
+#include "hook.h"
 
 static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
 
@@ -1700,22 +1701,22 @@ size_t ignore_non_trailer(const char *buf, size_t len)
 int run_commit_hook(int editor_is_used, const char *index_file,
 		    const char *name, ...)
 {
-	struct strvec hook_env = STRVEC_INIT;
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
 	va_list args;
-	int ret;
+	const char *arg;
 
-	strvec_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file);
+	strvec_pushf(&opt.env, "GIT_INDEX_FILE=%s", index_file);
 
 	/*
 	 * Let the hook know that no editor will be launched.
 	 */
 	if (!editor_is_used)
-		strvec_push(&hook_env, "GIT_EDITOR=:");
+		strvec_push(&opt.env, "GIT_EDITOR=:");
 
 	va_start(args, name);
-	ret = run_hook_ve(hook_env.v, name, args);
+	while ((arg = va_arg(args, const char *)))
+		strvec_push(&opt.args, arg);
 	va_end(args);
-	strvec_clear(&hook_env);
 
-	return ret;
+	return run_hooks_oneshot(name, &opt);
 }
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v3 11/13] read-cache: convert post-index-change to use hook.h
  2021-10-19 23:20 ` [PATCH v3 " Ævar Arnfjörð Bjarmason
                     ` (9 preceding siblings ...)
  2021-10-19 23:20   ` [PATCH v3 10/13] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h Ævar Arnfjörð Bjarmason
@ 2021-10-19 23:20   ` Ævar Arnfjörð Bjarmason
  2021-10-19 23:20   ` [PATCH v3 12/13] receive-pack: convert push-to-checkout hook to hook.h Ævar Arnfjörð Bjarmason
                     ` (3 subsequent siblings)
  14 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-19 23:20 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the post-index-change hook away from run-command.h to and over to
the new hook.h library.

This removes the last direct user of "run_hook_ve()" outside of
run-command.c ("run_hook_le()" still uses it). So we can make the
function static now. A subsequent commit will remove this code
entirely when "run_hook_le()" itself goes away.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 read-cache.c  | 11 ++++++++---
 run-command.c |  2 +-
 run-command.h |  1 -
 3 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/read-cache.c b/read-cache.c
index 8a50ff66b30..6674df28ab3 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -28,6 +28,7 @@
 #include "sparse-index.h"
 #include "csum-file.h"
 #include "promisor-remote.h"
+#include "hook.h"
 
 /* Mask for the name length in ce_flags in the on-disk index */
 
@@ -3101,6 +3102,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
 {
 	int ret;
 	int was_full = !istate->sparse_index;
+	struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;
 
 	ret = convert_to_sparse(istate, 0);
 
@@ -3129,9 +3131,12 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
 	else
 		ret = close_lock_file_gently(lock);
 
-	run_hook_le(NULL, "post-index-change",
-			istate->updated_workdir ? "1" : "0",
-			istate->updated_skipworktree ? "1" : "0", NULL);
+	strvec_pushl(&hook_opt.args,
+		     istate->updated_workdir ? "1" : "0",
+		     istate->updated_skipworktree ? "1" : "0",
+		     NULL);
+	run_hooks_oneshot("post-index-change", &hook_opt);
+
 	istate->updated_workdir = 0;
 	istate->updated_skipworktree = 0;
 
diff --git a/run-command.c b/run-command.c
index 7ef5cc712a9..d92e670c8ed 100644
--- a/run-command.c
+++ b/run-command.c
@@ -1323,7 +1323,7 @@ int async_with_fork(void)
 #endif
 }
 
-int run_hook_ve(const char *const *env, const char *name, va_list args)
+static int run_hook_ve(const char *const *env, const char *name, va_list args)
 {
 	struct child_process hook = CHILD_PROCESS_INIT;
 	const char *p;
diff --git a/run-command.h b/run-command.h
index 49878262584..3fa7454cf8a 100644
--- a/run-command.h
+++ b/run-command.h
@@ -239,7 +239,6 @@ int run_command(struct child_process *);
  */
 LAST_ARG_MUST_BE_NULL
 int run_hook_le(const char *const *env, const char *name, ...);
-int run_hook_ve(const char *const *env, const char *name, va_list args);
 
 /*
  * Trigger an auto-gc
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v3 12/13] receive-pack: convert push-to-checkout hook to hook.h
  2021-10-19 23:20 ` [PATCH v3 " Ævar Arnfjörð Bjarmason
                     ` (10 preceding siblings ...)
  2021-10-19 23:20   ` [PATCH v3 11/13] read-cache: convert post-index-change to use hook.h Ævar Arnfjörð Bjarmason
@ 2021-10-19 23:20   ` Ævar Arnfjörð Bjarmason
  2021-10-19 23:20   ` [PATCH v3 13/13] run-command: remove old run_hook_{le,ve}() hook API Ævar Arnfjörð Bjarmason
                     ` (2 subsequent siblings)
  14 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-19 23:20 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the push-to-checkout hook away from run-command.h to and over to
the new hook.h library.

This removes the last direct user of run_hook_le(), so we could remove
that function now, but let's leave that to a follow-up cleanup commit.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/receive-pack.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 25cc0c907e1..22e7a431728 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1435,9 +1435,12 @@ static const char *push_to_checkout(unsigned char *hash,
 				    struct strvec *env,
 				    const char *work_tree)
 {
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+
 	strvec_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree));
-	if (run_hook_le(env->v, push_to_checkout_hook,
-			hash_to_hex(hash), NULL))
+	strvec_pushv(&opt.env, env->v);
+	strvec_push(&opt.args, hash_to_hex(hash));
+	if (run_hooks_oneshot(push_to_checkout_hook, &opt))
 		return "push-to-checkout hook declined";
 	else
 		return NULL;
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v3 13/13] run-command: remove old run_hook_{le,ve}() hook API
  2021-10-19 23:20 ` [PATCH v3 " Ævar Arnfjörð Bjarmason
                     ` (11 preceding siblings ...)
  2021-10-19 23:20   ` [PATCH v3 12/13] receive-pack: convert push-to-checkout hook to hook.h Ævar Arnfjörð Bjarmason
@ 2021-10-19 23:20   ` Ævar Arnfjörð Bjarmason
  2021-10-29  6:27   ` [PATCH v3 00/13] hook.[ch]: new library to run hooks + simple hook conversion Junio C Hamano
  2021-11-01 18:56   ` [PATCH v4 00/17] " Ævar Arnfjörð Bjarmason
  14 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-19 23:20 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

The new hook.h library has replaced all run-command.h hook-related
functionality. So let's delete this dead code.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 run-command.c | 32 --------------------------------
 run-command.h | 16 ----------------
 2 files changed, 48 deletions(-)

diff --git a/run-command.c b/run-command.c
index d92e670c8ed..8a21ff525f3 100644
--- a/run-command.c
+++ b/run-command.c
@@ -1323,38 +1323,6 @@ int async_with_fork(void)
 #endif
 }
 
-static int run_hook_ve(const char *const *env, const char *name, va_list args)
-{
-	struct child_process hook = CHILD_PROCESS_INIT;
-	const char *p;
-
-	p = find_hook(name);
-	if (!p)
-		return 0;
-
-	strvec_push(&hook.args, p);
-	while ((p = va_arg(args, const char *)))
-		strvec_push(&hook.args, p);
-	hook.env = env;
-	hook.no_stdin = 1;
-	hook.stdout_to_stderr = 1;
-	hook.trace2_hook_name = name;
-
-	return run_command(&hook);
-}
-
-int run_hook_le(const char *const *env, const char *name, ...)
-{
-	va_list args;
-	int ret;
-
-	va_start(args, name);
-	ret = run_hook_ve(env, name, args);
-	va_end(args);
-
-	return ret;
-}
-
 struct io_pump {
 	/* initialized by caller */
 	int fd;
diff --git a/run-command.h b/run-command.h
index 3fa7454cf8a..59e1fbff64c 100644
--- a/run-command.h
+++ b/run-command.h
@@ -224,22 +224,6 @@ int finish_command_in_signal(struct child_process *);
  */
 int run_command(struct child_process *);
 
-/**
- * Run a hook.
- * The first argument is a pathname to an index file, or NULL
- * if the hook uses the default index file or no index is needed.
- * The second argument is the name of the hook.
- * The further arguments correspond to the hook arguments.
- * The last argument has to be NULL to terminate the arguments list.
- * If the hook does not exist or is not executable, the return
- * value will be zero.
- * If it is executable, the hook will be executed and the exit
- * status of the hook is returned.
- * On execution, .stdout_to_stderr and .no_stdin will be set.
- */
-LAST_ARG_MUST_BE_NULL
-int run_hook_le(const char *const *env, const char *name, ...);
-
 /*
  * Trigger an auto-gc
  */
-- 
2.33.1.1338.g20da966911a


^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v3 03/13] rebase: convert pre-rebase to use hook.h
  2021-10-19 23:20   ` [PATCH v3 03/13] rebase: convert pre-rebase to use hook.h Ævar Arnfjörð Bjarmason
@ 2021-10-29  5:48     ` Junio C Hamano
  0 siblings, 0 replies; 88+ messages in thread
From: Junio C Hamano @ 2021-10-29  5:48 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Phillip Wood, René Scharfe, Emily Shaffer, Bagas Sanjaya

Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:

> From: Emily Shaffer <emilyshaffer@google.com>
>
> Move the pre-rebase hook away from run-command.h to and over to the
> new hook.h library.
>
> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> ---
>  builtin/rebase.c | 8 ++++++--
>  1 file changed, 6 insertions(+), 2 deletions(-)
>
> diff --git a/builtin/rebase.c b/builtin/rebase.c
> index 34b4744e5f3..758b5dfabe2 100644
> --- a/builtin/rebase.c
> +++ b/builtin/rebase.c
> @@ -28,6 +28,7 @@
>  #include "sequencer.h"
>  #include "rebase-interactive.h"
>  #include "reset.h"
> +#include "hook.h"
>  
>  #define DEFAULT_REFLOG_ACTION "rebase"
>  
> @@ -1017,6 +1018,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>  	int reschedule_failed_exec = -1;
>  	int allow_preemptive_ff = 1;
>  	int preserve_merges_selected = 0;
> +	struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;
>  	struct option builtin_rebase_options[] = {
>  		OPT_STRING(0, "onto", &options.onto_name,
>  			   N_("revision"),
> @@ -1711,9 +1713,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
>  	}
>  
>  	/* If a hook exists, give it a chance to interrupt*/
> +	strvec_push(&hook_opt.args, options.upstream_arg);
> +	if (argc)
> +		strvec_push(&hook_opt.args, argv[0]);
>  	if (!ok_to_skip_pre_rebase &&
> -	    run_hook_le(NULL, "pre-rebase", options.upstream_arg,
> -			argc ? argv[0] : NULL, NULL))
> +	    run_hooks_oneshot("pre-rebase", &hook_opt))
>  		die(_("The pre-rebase hook refused to rebase."));
>  
>  	if (options.flags & REBASE_DIFFSTAT) {


OK, so the convention is

 * if there is no extra args the hook takes, you only give hook_name
   and NULL;

 * if there is even one arg the hook takes, you have to prepare the
   opt struct and strvec_push() the arg yourself.

That would work, and it might be merely the matter of taste, but it
would have been nicer if the calling sequence to run_hooks_oneshot()
were more like run_hook_le().

But patches were already written, so let's keep reading.


^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v3 05/13] hooks: convert 'post-checkout' hook to hook library
  2021-10-19 23:20   ` [PATCH v3 05/13] hooks: convert 'post-checkout' hook to hook library Ævar Arnfjörð Bjarmason
@ 2021-10-29  5:55     ` Junio C Hamano
  0 siblings, 0 replies; 88+ messages in thread
From: Junio C Hamano @ 2021-10-29  5:55 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Phillip Wood, René Scharfe, Emily Shaffer, Bagas Sanjaya

Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:

>  	if (!ret && opts->checkout) {
> -		const char *hook = find_hook("post-checkout");
> -		if (hook) {
> -			const char *env[] = { "GIT_DIR", "GIT_WORK_TREE", NULL };
> -			cp.git_cmd = 0;
> -			cp.no_stdin = 1;
> -			cp.stdout_to_stderr = 1;
> -			cp.dir = path;
> -			cp.env = env;
> -			cp.argv = NULL;
> -			cp.trace2_hook_name = "post-checkout";
> -			strvec_pushl(&cp.args, absolute_path(hook),
> -				     oid_to_hex(null_oid()),
> -				     oid_to_hex(&commit->object.oid),
> -				     "1", NULL);
> -			ret = run_command(&cp);
> -		}
> +		struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
> +
> +		strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL);
> +		strvec_pushl(&opt.args,
> +			     oid_to_hex(null_oid()),
> +			     oid_to_hex(&commit->object.oid),
> +			     "1",
> +			     NULL);
> +		opt.dir = path;
> +		opt.absolute_path = 1;
> +
> +		ret = run_hooks_oneshot("post-checkout", &opt);

I can see how passing opt from the caller gives more flexibility
like allowing to pass arbitrary environments.  The interface still
looks a bit unwieldy as I expect passing command line args would be
a much more common need than tweaking the environment, but let's
keep reading.



^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v3 07/13] git hook run: add an --ignore-missing flag
  2021-10-19 23:20   ` [PATCH v3 07/13] git hook run: add an --ignore-missing flag Ævar Arnfjörð Bjarmason
@ 2021-10-29  5:59     ` Junio C Hamano
  0 siblings, 0 replies; 88+ messages in thread
From: Junio C Hamano @ 2021-10-29  5:59 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Phillip Wood, René Scharfe, Emily Shaffer, Bagas Sanjaya

Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:

> +	int ignore_missing = 0;
>  	const char *hook_name;
>  	const char *hook_path;
>  	struct option run_options[] = {
> +		OPT_BOOL(0, "ignore-missing", &ignore_missing,
> +			 N_("silently ignore missing requested <hook-name>")),
>  		OPT_END(),
>  	};
>  	int ret;
> @@ -53,6 +56,8 @@ static int run(int argc, const char **argv, const char *prefix)
>  	git_config(git_default_config, NULL);
>  
>  	hook_name = argv[0];
> +	if (ignore_missing)
> +		return run_hooks_oneshot(hook_name, &opt);
>  	hook_path = find_hook(hook_name);
>  	if (!hook_path) {
>  		error("cannot find a hook named %s", hook_name);

Given that the rest of this "run()" function is more or less
identical to run_hooks_oneshot(), I have to wonder why we do not
give an .ignore_missing member to the hooks_opt structure to be done
with this.  Otehrwise, every time the underlying run_hooks()
interface needs to change, we need to update two places, no?


^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v3 00/13] hook.[ch]: new library to run hooks + simple hook conversion
  2021-10-19 23:20 ` [PATCH v3 " Ævar Arnfjörð Bjarmason
                     ` (12 preceding siblings ...)
  2021-10-19 23:20   ` [PATCH v3 13/13] run-command: remove old run_hook_{le,ve}() hook API Ævar Arnfjörð Bjarmason
@ 2021-10-29  6:27   ` Junio C Hamano
  2021-11-01 18:56   ` [PATCH v4 00/17] " Ævar Arnfjörð Bjarmason
  14 siblings, 0 replies; 88+ messages in thread
From: Junio C Hamano @ 2021-10-29  6:27 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Phillip Wood, René Scharfe, Emily Shaffer, Bagas Sanjaya

Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:

> Part 2 of the greater configurable hook saga, starting by converting
> some existing simple hooks to the new hook.[ch] library and "git hook
> run" utility.

These look mostly OK.  Other than .ignore_missing stuff, I think I
agree with the direction of the overall series.


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v4 00/17] hook.[ch]: new library to run hooks + simple hook conversion
  2021-10-19 23:20 ` [PATCH v3 " Ævar Arnfjörð Bjarmason
                     ` (13 preceding siblings ...)
  2021-10-29  6:27   ` [PATCH v3 00/13] hook.[ch]: new library to run hooks + simple hook conversion Junio C Hamano
@ 2021-11-01 18:56   ` Ævar Arnfjörð Bjarmason
  2021-11-01 18:56     ` [PATCH v4 01/17] hook: add 'run' subcommand Ævar Arnfjörð Bjarmason
                       ` (17 more replies)
  14 siblings, 18 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-01 18:56 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

Part 2 of the greater configurable hook saga, starting by converting
some existing simple hooks to the new hook.[ch] library and "git hook
run" utility. See [1] for the last iteration.

This re-roll should address Junio's comments on v3, i.e. there's now a
run_hooks_l() API similar to run_hook_le(). I also simplified the
callers of run_hooks(), which is now called run_hooks_opt().

Since no callers needed anything but a one-shot call the memory public
memory management API is gone, that'll eventually be needed for a "git
hook list" with configurable hooks, but let's introduce those API bits
then, not now.

This grew to 17 patches from 13 due to splitting up some commints for
ease of review, the overall diffstat got smaller. I picked a function
with the same width as the old function, so many of the diffs are
now simply a s/run_hook_le(NULL, /run_hooks_l(/.

1. https://lore.kernel.org/git/cover-v3-00.13-00000000000-20211019T231647Z-avarab@gmail.com/

Emily Shaffer (14):
  hook: add 'run' subcommand
  gc: use hook library for pre-auto-gc hook
  am: convert {pre,post}-applypatch to use hook.h
  rebase: convert pre-rebase to use hook.h
  am: convert applypatch-msg to use hook.h
  merge: convert post-merge to use hook.h
  hooks: convert non-worktree 'post-checkout' hook to hook library
  hooks: convert worktree 'post-checkout' hook to hook library
  send-email: use 'git hook run' for 'sendemail-validate'
  git-p4: use 'git hook' to run hooks
  commit: convert {pre-commit,prepare-commit-msg} hook to hook.h
  read-cache: convert post-index-change to use hook.h
  receive-pack: convert push-to-checkout hook to hook.h
  run-command: remove old run_hook_{le,ve}() hook API

Ævar Arnfjörð Bjarmason (3):
  hook API: add a run_hooks() wrapper
  hook API: add a run_hooks_l() wrapper
  git hook run: add an --ignore-missing flag

 .gitignore                 |   1 +
 Documentation/git-hook.txt |  45 +++++++++++++
 Documentation/githooks.txt |   4 ++
 Makefile                   |   1 +
 builtin.h                  |   1 +
 builtin/am.c               |   6 +-
 builtin/checkout.c         |   3 +-
 builtin/clone.c            |   3 +-
 builtin/gc.c               |   3 +-
 builtin/hook.c             |  84 +++++++++++++++++++++++
 builtin/merge.c            |   2 +-
 builtin/rebase.c           |   3 +-
 builtin/receive-pack.c     |   7 +-
 builtin/worktree.c         |  27 +++-----
 command-list.txt           |   1 +
 commit.c                   |  15 +++--
 git-p4.py                  |  70 ++-----------------
 git-send-email.perl        |  22 +++---
 git.c                      |   1 +
 hook.c                     | 131 ++++++++++++++++++++++++++++++++++++
 hook.h                     |  57 ++++++++++++++++
 read-cache.c               |   3 +-
 reset.c                    |   3 +-
 run-command.c              |  32 ---------
 run-command.h              |  17 -----
 t/t1800-hook.sh            | 134 +++++++++++++++++++++++++++++++++++++
 t/t9001-send-email.sh      |   4 +-
 27 files changed, 522 insertions(+), 158 deletions(-)
 create mode 100644 Documentation/git-hook.txt
 create mode 100644 builtin/hook.c
 create mode 100755 t/t1800-hook.sh

Range-diff against v3:
 1:  02fd699e699 !  1:  d97d6734961 hook: add 'run' subcommand
    @@ Commit message
         builtin/{commit-graph,multi-pack-index}.c.
     
         Some of the implementation here, such as a function being named
    -    run_hooks() when it's tasked with running one hook, to using the
    +    run_hooks_opt() when it's tasked with running one hook, to using the
         run_processes_parallel_tr2() API to run with jobs=1 is somewhere
         between a bit odd and and an overkill for the current features of this
         "hook run" command and the hook.[ch] API.
    @@ builtin/hook.c (new)
     +	int i;
     +	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
     +	const char *hook_name;
    -+	const char *hook_path;
     +	struct option run_options[] = {
     +		OPT_END(),
     +	};
    @@ builtin/hook.c (new)
     +	git_config(git_default_config, NULL);
     +
     +	hook_name = argv[0];
    -+	hook_path = find_hook(hook_name);
    -+	if (!hook_path) {
    -+		error("cannot find a hook named %s", hook_name);
    -+		return 1;
    -+	}
    -+
    -+	ret = run_hooks(hook_name, hook_path, &opt);
    -+	run_hooks_opt_clear(&opt);
    ++	opt.error_if_missing = 1;
    ++	ret = run_hooks_opt(hook_name, &opt);
    ++	if (ret < 0) /* error() return */
    ++		ret = 1;
     +	return ret;
     +usage:
     +	usage_with_options(builtin_hook_run_usage, run_options);
    @@ hook.c: int hook_exists(const char *name)
      	return !!find_hook(name);
      }
     +
    -+void run_hooks_opt_clear(struct run_hooks_opt *o)
    -+{
    -+	strvec_clear(&o->env);
    -+	strvec_clear(&o->args);
    -+}
    -+
     +static int pick_next_hook(struct child_process *cp,
     +			  struct strbuf *out,
     +			  void *pp_cb,
    @@ hook.c: int hook_exists(const char *name)
     +	return 0;
     +}
     +
    -+int run_hooks(const char *hook_name, const char *hook_path,
    -+	      struct run_hooks_opt *options)
    ++static void run_hooks_opt_clear(struct run_hooks_opt *options)
    ++{
    ++	strvec_clear(&options->env);
    ++	strvec_clear(&options->args);
    ++}
    ++
    ++int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)
     +{
     +	struct hook_cb_data cb_data = {
     +		.rc = 0,
     +		.hook_name = hook_name,
    -+		.hook_path = hook_path,
     +		.options = options,
     +	};
    ++	const char *const hook_path = find_hook(hook_name);
     +	int jobs = 1;
    ++	int ret = 0;
     +
     +	if (!options)
     +		BUG("a struct run_hooks_opt must be provided to run_hooks");
     +
    ++	if (!hook_path && !options->error_if_missing)
    ++		goto cleanup;
    ++
    ++	if (!hook_path) {
    ++		ret = error("cannot find a hook named %s", hook_name);
    ++		goto cleanup;
    ++	}
    ++
    ++	cb_data.hook_path = hook_path;
     +	run_processes_parallel_tr2(jobs,
     +				   pick_next_hook,
     +				   notify_start_failure,
    @@ hook.c: int hook_exists(const char *name)
     +				   &cb_data,
     +				   "hook",
     +				   hook_name);
    -+
    -+	return cb_data.rc;
    ++	ret = cb_data.rc;
    ++cleanup:
    ++	run_hooks_opt_clear(options);
    ++	return ret;
     +}
     
      ## hook.h ##
    @@ hook.h
     +
     +	/* Args to be passed to each hook */
     +	struct strvec args;
    ++
    ++	/* Emit an error if the hook is missing */
    ++	unsigned int error_if_missing:1;
     +};
     +
     +#define RUN_HOOKS_OPT_INIT { \
    @@ hook.h: const char *find_hook(const char *name);
      int hook_exists(const char *hookname);
      
     +/**
    -+ * Clear data from an initialized "struct run_hooks_opt".
    -+ */
    -+void run_hooks_opt_clear(struct run_hooks_opt *o);
    -+
    -+/**
    -+ * Takes an already resolved hook found via find_hook() and runs
    -+ * it. Does not call run_hooks_opt_clear() for you.
    ++ * Takes a `hook_name`, resolves it to a path with find_hook(), and
    ++ * runs the hook for you with the options specified in "struct
    ++ * run_hooks opt". Will free memory associated with the "struct run_hooks_opt".
    ++ *
    ++ * Returns the status code of the run hook, or a negative value on
    ++ * error().
     + */
    -+int run_hooks(const char *hookname, const char *hook_path,
    -+	      struct run_hooks_opt *options);
    ++int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);
      #endif
     
      ## t/t1800-hook.sh (new) ##
 -:  ----------- >  2:  ca6464f7d5e hook API: add a run_hooks() wrapper
 2:  42cc4d2c3c6 !  3:  173860afca1 gc: use hook library for pre-auto-gc hook
    @@ Commit message
         gc: use hook library for pre-auto-gc hook
     
         Move the pre-auto-gc hook away from run-command.h to and over to the
    -    new hook.h library.
    -
    -    To do this introduce a simple run_hooks_oneshot() wrapper, we'll be
    -    using it extensively for these simple cases of wanting to run a single
    -    hook under a given name, and having it free the memory we allocate for
    -    us.
    +    new hook.h library. This uses the new run_hooks() wrapper.
     
         Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
    @@ builtin/gc.c: static int need_to_gc(void)
      		return 0;
      
     -	if (run_hook_le(NULL, "pre-auto-gc", NULL))
    -+	if (run_hooks_oneshot("pre-auto-gc", NULL))
    ++	if (run_hooks("pre-auto-gc"))
      		return 0;
      	return 1;
      }
    -
    - ## hook.c ##
    -@@ hook.c: int run_hooks(const char *hook_name, const char *hook_path,
    - 
    - 	return cb_data.rc;
    - }
    -+
    -+int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options)
    -+{
    -+	const char *hook_path;
    -+	struct run_hooks_opt hook_opt_scratch = RUN_HOOKS_OPT_INIT;
    -+	int ret = 0;
    -+
    -+	if (!options)
    -+		options = &hook_opt_scratch;
    -+
    -+	hook_path = find_hook(hook_name);
    -+	if (!hook_path)
    -+		goto cleanup;
    -+
    -+	ret = run_hooks(hook_name, hook_path, options);
    -+cleanup:
    -+	run_hooks_opt_clear(options);
    -+
    -+	return ret;
    -+}
    -
    - ## hook.h ##
    -@@ hook.h: void run_hooks_opt_clear(struct run_hooks_opt *o);
    - /**
    -  * Takes an already resolved hook found via find_hook() and runs
    -  * it. Does not call run_hooks_opt_clear() for you.
    -+ *
    -+ * See run_hooks_oneshot() for the simpler one-shot API.
    -  */
    - int run_hooks(const char *hookname, const char *hook_path,
    - 	      struct run_hooks_opt *options);
    -+
    -+/**
    -+ * Calls find_hook() on your "hook_name" and runs the hooks (if any)
    -+ * with run_hooks().
    -+ *
    -+ * If "options" is provided calls run_hooks_opt_clear() on it for
    -+ * you. If "options" is NULL the default options from
    -+ * RUN_HOOKS_OPT_INIT will be used.
    -+ */
    -+int run_hooks_oneshot(const char *hook_name, struct run_hooks_opt *options);
    -+
    - #endif
 4:  b26cef24f39 !  4:  80a2171ddaf am: convert applypatch to use hook.h
    @@ Metadata
     Author: Emily Shaffer <emilyshaffer@google.com>
     
      ## Commit message ##
    -    am: convert applypatch to use hook.h
    +    am: convert {pre,post}-applypatch to use hook.h
     
    -    Teach pre-applypatch, post-applypatch, and applypatch-msg to use the
    -    hook.h library instead of the run-command.h library.
    +    Teach pre-applypatch and post-applypatch to use the hook.h library
    +    instead of the run-command.h library.
     
         Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
      ## builtin/am.c ##
    -@@ builtin/am.c: static void am_destroy(const struct am_state *state)
    - static int run_applypatch_msg_hook(struct am_state *state)
    - {
    - 	int ret;
    -+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
    - 
    - 	assert(state->msg);
    --	ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL);
    -+	strvec_push(&opt.args, am_path(state, "final-commit"));
    -+	ret = run_hooks_oneshot("applypatch-msg", &opt);
    - 
    - 	if (!ret) {
    - 		FREE_AND_NULL(state->msg);
     @@ builtin/am.c: static void do_commit(const struct am_state *state)
      	const char *reflog_msg, *author, *committer = NULL;
      	struct strbuf sb = STRBUF_INIT;
      
     -	if (run_hook_le(NULL, "pre-applypatch", NULL))
    -+	if (run_hooks_oneshot("pre-applypatch", NULL))
    ++	if (run_hooks("pre-applypatch"))
      		exit(1);
      
      	if (write_cache_as_tree(&tree, 0, NULL))
    @@ builtin/am.c: static void do_commit(const struct am_state *state)
      	}
      
     -	run_hook_le(NULL, "post-applypatch", NULL);
    -+	run_hooks_oneshot("post-applypatch", NULL);
    ++	run_hooks("post-applypatch");
      
      	strbuf_release(&sb);
      }
 -:  ----------- >  5:  74f459db287 hook API: add a run_hooks_l() wrapper
 3:  cbbfd77a4f6 !  6:  1fd70c0e88a rebase: convert pre-rebase to use hook.h
    @@ Commit message
         Move the pre-rebase hook away from run-command.h to and over to the
         new hook.h library.
     
    +    Since this hook needs arguments introduce a run_hooksl() wrapper, like
    +    run_hooks(), but it takes varargs.
    +
         Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
    @@ builtin/rebase.c
      #define DEFAULT_REFLOG_ACTION "rebase"
      
     @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)
    - 	int reschedule_failed_exec = -1;
    - 	int allow_preemptive_ff = 1;
    - 	int preserve_merges_selected = 0;
    -+	struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;
    - 	struct option builtin_rebase_options[] = {
    - 		OPT_STRING(0, "onto", &options.onto_name,
    - 			   N_("revision"),
    -@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)
    - 	}
      
      	/* If a hook exists, give it a chance to interrupt*/
    -+	strvec_push(&hook_opt.args, options.upstream_arg);
    -+	if (argc)
    -+		strvec_push(&hook_opt.args, argv[0]);
      	if (!ok_to_skip_pre_rebase &&
     -	    run_hook_le(NULL, "pre-rebase", options.upstream_arg,
    --			argc ? argv[0] : NULL, NULL))
    -+	    run_hooks_oneshot("pre-rebase", &hook_opt))
    ++	    run_hooks_l("pre-rebase", options.upstream_arg,
    + 			argc ? argv[0] : NULL, NULL))
      		die(_("The pre-rebase hook refused to rebase."));
      
    - 	if (options.flags & REBASE_DIFFSTAT) {
 -:  ----------- >  7:  ccba3ddf52e am: convert applypatch-msg to use hook.h
 6:  7a9fd8627cd !  8:  2c23e8645ec merge: convert post-merge to use hook.h
    @@ Commit message
         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
      ## builtin/merge.c ##
    -@@ builtin/merge.c: static void finish(struct commit *head_commit,
    - 		   const struct object_id *new_head, const char *msg)
    - {
    - 	struct strbuf reflog_message = STRBUF_INIT;
    -+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
    - 	const struct object_id *head = &head_commit->object.oid;
    - 
    - 	if (!msg)
     @@ builtin/merge.c: static void finish(struct commit *head_commit,
      	}
      
      	/* Run a post-merge hook */
     -	run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL);
    -+	strvec_push(&opt.args, squash ? "1" : "0");
    -+	run_hooks_oneshot("post-merge", &opt);
    ++	run_hooks_l("post-merge", squash ? "1" : "0", NULL);
      
      	apply_autostash(git_path_merge_autostash(the_repository));
      	strbuf_release(&reflog_message);
 -:  ----------- >  9:  cb95c79093b hooks: convert non-worktree 'post-checkout' hook to hook library
 5:  2a747a65829 ! 10:  f330600fec8 hooks: convert 'post-checkout' hook to hook library
    @@ Metadata
     Author: Emily Shaffer <emilyshaffer@google.com>
     
      ## Commit message ##
    -    hooks: convert 'post-checkout' hook to hook library
    +    hooks: convert worktree 'post-checkout' hook to hook library
     
         Move the running of the 'post-checkout' hook away from run-command.h
    -    to the new hook.h library. For "worktree" this requires a change to it
    -    to run the hooks from a given directory.
    +    to the new hook.h library in builtin/worktree.c. For this special case
    +    we need a change to the hook API to teach it to run the hook from a
    +    given directory.
     
    -    We could strictly speaking skip the "absolute_path" flag and just
    -    check if "dir" is specified, but let's split them up for clarity, as
    -    well as for any future user who'd like to set "dir" but not implicitly
    -    change the argument to an absolute path.
    +    We cannot skip the "absolute_path" flag and just check if "dir" is
    +    specified as we'd then fail to find our hook in the new dir we'd
    +    chdir() to. We currently don't have a use-case for running a hook not
    +    in our "base" repository at a given absolute path, so let's have "dir"
    +    imply absolute_path(find_hook(hook_name)).
     
         Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
    - ## builtin/checkout.c ##
    -@@
    - #include "config.h"
    - #include "diff.h"
    - #include "dir.h"
    -+#include "hook.h"
    - #include "ll-merge.h"
    - #include "lockfile.h"
    - #include "merge-recursive.h"
    -@@ builtin/checkout.c: struct branch_info {
    - static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,
    - 			      int changed)
    - {
    --	return run_hook_le(NULL, "post-checkout",
    --			   oid_to_hex(old_commit ? &old_commit->object.oid : null_oid()),
    --			   oid_to_hex(new_commit ? &new_commit->object.oid : null_oid()),
    --			   changed ? "1" : "0", NULL);
    -+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
    -+
    - 	/* "new_commit" can be NULL when checking out from the index before
    - 	   a commit exists. */
    --
    -+	strvec_pushl(&opt.args,
    -+		     oid_to_hex(old_commit ? &old_commit->object.oid : null_oid()),
    -+		     oid_to_hex(new_commit ? &new_commit->object.oid : null_oid()),
    -+		     changed ? "1" : "0",
    -+		     NULL);
    -+	return run_hooks_oneshot("post-checkout", &opt);
    - }
    - 
    - static int update_some(const struct object_id *oid, struct strbuf *base,
    -
    - ## builtin/clone.c ##
    -@@
    - #include "connected.h"
    - #include "packfile.h"
    - #include "list-objects-filter-options.h"
    -+#include "hook.h"
    - 
    - /*
    -  * Overall FIXMEs:
    -@@ builtin/clone.c: static int checkout(int submodule_progress)
    - 	struct tree *tree;
    - 	struct tree_desc t;
    - 	int err = 0;
    -+	struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;
    - 
    - 	if (option_no_checkout)
    - 		return 0;
    -@@ builtin/clone.c: static int checkout(int submodule_progress)
    - 	if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
    - 		die(_("unable to write new index file"));
    - 
    --	err |= run_hook_le(NULL, "post-checkout", oid_to_hex(null_oid()),
    --			   oid_to_hex(&oid), "1", NULL);
    -+	strvec_pushl(&hook_opt.args, oid_to_hex(null_oid()), oid_to_hex(&oid),
    -+		     "1", NULL);
    -+	err |= run_hooks_oneshot("post-checkout", &hook_opt);
    - 
    - 	if (!err && (option_recurse_submodules.nr > 0)) {
    - 		struct strvec args = STRVEC_INIT;
    -
      ## builtin/worktree.c ##
     @@ builtin/worktree.c: static int add_worktree(const char *path, const char *refname,
      	 * is_junk is cleared, but do return appropriate code when hook fails.
    @@ builtin/worktree.c: static int add_worktree(const char *path, const char *refnam
     +			     "1",
     +			     NULL);
     +		opt.dir = path;
    -+		opt.absolute_path = 1;
     +
    -+		ret = run_hooks_oneshot("post-checkout", &opt);
    ++		ret = run_hooks_opt("post-checkout", &opt);
      	}
      
      	strvec_clear(&child_env);
    @@ hook.c: static int pick_next_hook(struct child_process *cp,
      
      	strvec_push(&cp->args, hook_path);
      	strvec_pushv(&cp->args, hook_cb->options->args.v);
    -@@ hook.c: static int notify_hook_finished(int result,
    - int run_hooks(const char *hook_name, const char *hook_path,
    - 	      struct run_hooks_opt *options)
    +@@ hook.c: static void run_hooks_opt_clear(struct run_hooks_opt *options)
    + 
    + int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)
      {
     +	struct strbuf abs_path = STRBUF_INIT;
      	struct hook_cb_data cb_data = {
      		.rc = 0,
      		.hook_name = hook_name,
    --		.hook_path = hook_path,
    - 		.options = options,
    - 	};
    - 	int jobs = 1;
    -@@ hook.c: int run_hooks(const char *hook_name, const char *hook_path,
    - 	if (!options)
    - 		BUG("a struct run_hooks_opt must be provided to run_hooks");
    +@@ hook.c: int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)
    + 	}
      
    -+	if (options->absolute_path) {
    + 	cb_data.hook_path = hook_path;
    ++	if (options->dir) {
     +		strbuf_add_absolute_path(&abs_path, hook_path);
    -+		hook_path = abs_path.buf;
    ++		cb_data.hook_path = abs_path.buf;
     +	}
    -+	cb_data.hook_path = hook_path;
     +
      	run_processes_parallel_tr2(jobs,
      				   pick_next_hook,
      				   notify_start_failure,
    -@@ hook.c: int run_hooks(const char *hook_name, const char *hook_path,
    - 				   "hook",
    +@@ hook.c: int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)
      				   hook_name);
    - 
    -+	if (options->absolute_path)
    -+		strbuf_release(&abs_path);
    -+
    - 	return cb_data.rc;
    + 	ret = cb_data.rc;
    + cleanup:
    ++	strbuf_release(&abs_path);
    + 	run_hooks_opt_clear(options);
    + 	return ret;
      }
    - 
     
      ## hook.h ##
     @@ hook.h: struct run_hooks_opt
      
    - 	/* Args to be passed to each hook */
    - 	struct strvec args;
    + 	/* Emit an error if the hook is missing */
    + 	unsigned int error_if_missing:1;
     +
    -+	/*
    -+	 * Resolve and run the "absolute_path(hook)" instead of
    -+	 * "hook". Used for "git worktree" hooks
    ++	/**
    ++	 * An optional initial working directory for the hook,
    ++	 * translates to "struct child_process"'s "dir" member.
     +	 */
    -+	int absolute_path;
    -+
    -+	/* Path to initial working directory for subprocess */
     +	const char *dir;
      };
      
      #define RUN_HOOKS_OPT_INIT { \
    -
    - ## reset.c ##
    -@@
    - #include "tree-walk.h"
    - #include "tree.h"
    - #include "unpack-trees.h"
    -+#include "hook.h"
    - 
    - int reset_head(struct repository *r, struct object_id *oid, const char *action,
    - 	       const char *switch_to_branch, unsigned flags,
    -@@ reset.c: int reset_head(struct repository *r, struct object_id *oid, const char *action,
    - 			ret = create_symref("HEAD", switch_to_branch,
    - 					    reflog_head);
    - 	}
    --	if (run_hook)
    --		run_hook_le(NULL, "post-checkout",
    --			    oid_to_hex(orig ? orig : null_oid()),
    --			    oid_to_hex(oid), "1", NULL);
    -+	if (run_hook) {
    -+		struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
    -+		strvec_pushl(&opt.args,
    -+			     oid_to_hex(orig ? orig : null_oid()),
    -+			     oid_to_hex(oid),
    -+			     "1",
    -+			     NULL);
    -+		run_hooks_oneshot("post-checkout", &opt);
    -+	}
    - 
    - leave_reset_head:
    - 	strbuf_release(&msg);
 7:  840fb530df3 ! 11:  a0b6818c766 git hook run: add an --ignore-missing flag
    @@ Commit message
         git hook run: add an --ignore-missing flag
     
         For certain one-shot hooks we'd like to optimistically run them, and
    -    not complain if they don't exist. This will be used by send-email in a
    -    subsequent commit.
    +    not complain if they don't exist.
    +
    +    This was already supported by the underlying hook.c library, but had
    +    not been exposed via "git hook run". The command version of this will
    +    be used by send-email in a subsequent commit.
     
         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
    @@ builtin/hook.c: static int run(int argc, const char **argv, const char *prefix)
      	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
     +	int ignore_missing = 0;
      	const char *hook_name;
    - 	const char *hook_path;
      	struct option run_options[] = {
     +		OPT_BOOL(0, "ignore-missing", &ignore_missing,
     +			 N_("silently ignore missing requested <hook-name>")),
    @@ builtin/hook.c: static int run(int argc, const char **argv, const char *prefix)
      	git_config(git_default_config, NULL);
      
      	hook_name = argv[0];
    -+	if (ignore_missing)
    -+		return run_hooks_oneshot(hook_name, &opt);
    - 	hook_path = find_hook(hook_name);
    - 	if (!hook_path) {
    - 		error("cannot find a hook named %s", hook_name);
    +-	opt.error_if_missing = 1;
    ++	if (!ignore_missing)
    ++		opt.error_if_missing = 1;
    + 	ret = run_hooks_opt(hook_name, &opt);
    + 	if (ret < 0) /* error() return */
    + 		ret = 1;
     
      ## t/t1800-hook.sh ##
     @@ t/t1800-hook.sh: test_expect_success 'git hook run: nonexistent hook' '
 8:  716ebabd794 = 12:  efa35971e9f send-email: use 'git hook run' for 'sendemail-validate'
 9:  95782109270 = 13:  98e0e3330fb git-p4: use 'git hook' to run hooks
10:  706426c8a79 ! 14:  79ea5a2a4f5 commit: convert {pre-commit,prepare-commit-msg} hook to hook.h
    @@ commit.c: size_t ignore_non_trailer(const char *buf, size_t len)
     -	strvec_clear(&hook_env);
      
     -	return ret;
    -+	return run_hooks_oneshot(name, &opt);
    ++	return run_hooks_opt(name, &opt);
      }
11:  39069a9c3ff ! 15:  81612f94707 read-cache: convert post-index-change to use hook.h
    @@ read-cache.c
      
      /* Mask for the name length in ce_flags in the on-disk index */
      
    -@@ read-cache.c: static int do_write_locked_index(struct index_state *istate, struct lock_file *l
    - {
    - 	int ret;
    - 	int was_full = !istate->sparse_index;
    -+	struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;
    - 
    - 	ret = convert_to_sparse(istate, 0);
    - 
     @@ read-cache.c: static int do_write_locked_index(struct index_state *istate, struct lock_file *l
      	else
      		ret = close_lock_file_gently(lock);
      
     -	run_hook_le(NULL, "post-index-change",
    --			istate->updated_workdir ? "1" : "0",
    --			istate->updated_skipworktree ? "1" : "0", NULL);
    -+	strvec_pushl(&hook_opt.args,
    -+		     istate->updated_workdir ? "1" : "0",
    -+		     istate->updated_skipworktree ? "1" : "0",
    -+		     NULL);
    -+	run_hooks_oneshot("post-index-change", &hook_opt);
    -+
    ++	run_hooks_l("post-index-change",
    + 			istate->updated_workdir ? "1" : "0",
    + 			istate->updated_skipworktree ? "1" : "0", NULL);
      	istate->updated_workdir = 0;
    - 	istate->updated_skipworktree = 0;
    - 
     
      ## run-command.c ##
     @@ run-command.c: int async_with_fork(void)
12:  9818078f1e5 ! 16:  43ecd6697e0 receive-pack: convert push-to-checkout hook to hook.h
    @@ builtin/receive-pack.c: static const char *push_to_checkout(unsigned char *hash,
     -			hash_to_hex(hash), NULL))
     +	strvec_pushv(&opt.env, env->v);
     +	strvec_push(&opt.args, hash_to_hex(hash));
    -+	if (run_hooks_oneshot(push_to_checkout_hook, &opt))
    ++	if (run_hooks_opt(push_to_checkout_hook, &opt))
      		return "push-to-checkout hook declined";
      	else
      		return NULL;
13:  1bc080d3611 = 17:  9ef574fa30c run-command: remove old run_hook_{le,ve}() hook API
-- 
2.33.1.1570.g069344fdd45


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v4 01/17] hook: add 'run' subcommand
  2021-11-01 18:56   ` [PATCH v4 00/17] " Ævar Arnfjörð Bjarmason
@ 2021-11-01 18:56     ` Ævar Arnfjörð Bjarmason
  2021-11-01 18:56     ` [PATCH v4 02/17] hook API: add a run_hooks() wrapper Ævar Arnfjörð Bjarmason
                       ` (16 subsequent siblings)
  17 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-01 18:56 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

In order to enable hooks to be run as an external process, by a
standalone Git command, or by tools which wrap Git, provide an external
means to run all configured hook commands for a given hook event.

Most of our hooks require more complex functionality than this, but
let's start with the bare minimum required to support our simplest
hooks.

In terms of implementation the usage_with_options() and "goto usage"
pattern here mirrors that of
builtin/{commit-graph,multi-pack-index}.c.

Some of the implementation here, such as a function being named
run_hooks_opt() when it's tasked with running one hook, to using the
run_processes_parallel_tr2() API to run with jobs=1 is somewhere
between a bit odd and and an overkill for the current features of this
"hook run" command and the hook.[ch] API.

This code will eventually be able to run multiple hooks declared in
config in parallel, by starting out with these names and APIs we
reduce the later churn of renaming functions, switching from the
run_command() to run_processes_parallel_tr2() API etc.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 .gitignore                 |   1 +
 Documentation/git-hook.txt |  37 +++++++++++
 Documentation/githooks.txt |   4 ++
 Makefile                   |   1 +
 builtin.h                  |   1 +
 builtin/hook.c             |  80 +++++++++++++++++++++++
 command-list.txt           |   1 +
 git.c                      |   1 +
 hook.c                     | 102 +++++++++++++++++++++++++++++
 hook.h                     |  35 ++++++++++
 t/t1800-hook.sh            | 129 +++++++++++++++++++++++++++++++++++++
 11 files changed, 392 insertions(+)
 create mode 100644 Documentation/git-hook.txt
 create mode 100644 builtin/hook.c
 create mode 100755 t/t1800-hook.sh

diff --git a/.gitignore b/.gitignore
index 054249b20a8..f817c509ec0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -77,6 +77,7 @@
 /git-grep
 /git-hash-object
 /git-help
+/git-hook
 /git-http-backend
 /git-http-fetch
 /git-http-push
diff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt
new file mode 100644
index 00000000000..e39b1b5d069
--- /dev/null
+++ b/Documentation/git-hook.txt
@@ -0,0 +1,37 @@
+git-hook(1)
+===========
+
+NAME
+----
+git-hook - Run git hooks
+
+SYNOPSIS
+--------
+[verse]
+'git hook' run <hook-name> [-- <hook-args>]
+
+DESCRIPTION
+-----------
+
+A command interface to running git hooks (see linkgit:githooks[5]),
+for use by other scripted git commands.
+
+SUBCOMMANDS
+-----------
+
+run::
+	Run the `<hook-name>` hook. See linkgit:githooks[5] for
+	supported hook names.
++
+
+Any positional arguments to the hook should be passed after a
+mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See
+linkgit:githooks[5] for arguments hooks might expect (if any).
+
+SEE ALSO
+--------
+linkgit:githooks[5]
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index b51959ff941..a16e62bc8c8 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -698,6 +698,10 @@ and "0" meaning they were not.
 Only one parameter should be set to "1" when the hook runs.  The hook
 running passing "1", "1" should not be possible.
 
+SEE ALSO
+--------
+linkgit:git-hook[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/Makefile b/Makefile
index 12be39ac497..6cd20534304 100644
--- a/Makefile
+++ b/Makefile
@@ -1107,6 +1107,7 @@ BUILTIN_OBJS += builtin/get-tar-commit-id.o
 BUILTIN_OBJS += builtin/grep.o
 BUILTIN_OBJS += builtin/hash-object.o
 BUILTIN_OBJS += builtin/help.o
+BUILTIN_OBJS += builtin/hook.o
 BUILTIN_OBJS += builtin/index-pack.o
 BUILTIN_OBJS += builtin/init-db.o
 BUILTIN_OBJS += builtin/interpret-trailers.o
diff --git a/builtin.h b/builtin.h
index 8a58743ed63..83379f3832c 100644
--- a/builtin.h
+++ b/builtin.h
@@ -164,6 +164,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);
 int cmd_grep(int argc, const char **argv, const char *prefix);
 int cmd_hash_object(int argc, const char **argv, const char *prefix);
 int cmd_help(int argc, const char **argv, const char *prefix);
+int cmd_hook(int argc, const char **argv, const char *prefix);
 int cmd_index_pack(int argc, const char **argv, const char *prefix);
 int cmd_init_db(int argc, const char **argv, const char *prefix);
 int cmd_interpret_trailers(int argc, const char **argv, const char *prefix);
diff --git a/builtin/hook.c b/builtin/hook.c
new file mode 100644
index 00000000000..9b67ff50cef
--- /dev/null
+++ b/builtin/hook.c
@@ -0,0 +1,80 @@
+#include "cache.h"
+#include "builtin.h"
+#include "config.h"
+#include "hook.h"
+#include "parse-options.h"
+#include "strbuf.h"
+#include "strvec.h"
+
+#define BUILTIN_HOOK_RUN_USAGE \
+	N_("git hook run <hook-name> [-- <hook-args>]")
+
+static const char * const builtin_hook_usage[] = {
+	BUILTIN_HOOK_RUN_USAGE,
+	NULL
+};
+
+static const char * const builtin_hook_run_usage[] = {
+	BUILTIN_HOOK_RUN_USAGE,
+	NULL
+};
+
+static int run(int argc, const char **argv, const char *prefix)
+{
+	int i;
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+	const char *hook_name;
+	struct option run_options[] = {
+		OPT_END(),
+	};
+	int ret;
+
+	argc = parse_options(argc, argv, prefix, run_options,
+			     builtin_hook_run_usage,
+			     PARSE_OPT_KEEP_DASHDASH);
+
+	if (!argc)
+		goto usage;
+
+	/*
+	 * Having a -- for "run" when providing <hook-args> is
+	 * mandatory.
+	 */
+	if (argc > 1 && strcmp(argv[1], "--") &&
+	    strcmp(argv[1], "--end-of-options"))
+		goto usage;
+
+	/* Add our arguments, start after -- */
+	for (i = 2 ; i < argc; i++)
+		strvec_push(&opt.args, argv[i]);
+
+	/* Need to take into account core.hooksPath */
+	git_config(git_default_config, NULL);
+
+	hook_name = argv[0];
+	opt.error_if_missing = 1;
+	ret = run_hooks_opt(hook_name, &opt);
+	if (ret < 0) /* error() return */
+		ret = 1;
+	return ret;
+usage:
+	usage_with_options(builtin_hook_run_usage, run_options);
+}
+
+int cmd_hook(int argc, const char **argv, const char *prefix)
+{
+	struct option builtin_hook_options[] = {
+		OPT_END(),
+	};
+
+	argc = parse_options(argc, argv, NULL, builtin_hook_options,
+			     builtin_hook_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+	if (!argc)
+		goto usage;
+
+	if (!strcmp(argv[0], "run"))
+		return run(argc, argv, prefix);
+
+usage:
+	usage_with_options(builtin_hook_usage, builtin_hook_options);
+}
diff --git a/command-list.txt b/command-list.txt
index eb9cee8dee9..0c9af14bdf3 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -103,6 +103,7 @@ git-grep                                mainporcelain           info
 git-gui                                 mainporcelain
 git-hash-object                         plumbingmanipulators
 git-help                                ancillaryinterrogators          complete
+git-hook                                purehelpers
 git-http-backend                        synchingrepositories
 git-http-fetch                          synchelpers
 git-http-push                           synchelpers
diff --git a/git.c b/git.c
index 5ff21be21f3..a5be02b04b8 100644
--- a/git.c
+++ b/git.c
@@ -538,6 +538,7 @@ static struct cmd_struct commands[] = {
 	{ "grep", cmd_grep, RUN_SETUP_GENTLY },
 	{ "hash-object", cmd_hash_object },
 	{ "help", cmd_help },
+	{ "hook", cmd_hook, RUN_SETUP },
 	{ "index-pack", cmd_index_pack, RUN_SETUP_GENTLY | NO_PARSEOPT },
 	{ "init", cmd_init_db },
 	{ "init-db", cmd_init_db },
diff --git a/hook.c b/hook.c
index 55e1145a4b7..64d3608e45e 100644
--- a/hook.c
+++ b/hook.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "hook.h"
 #include "run-command.h"
+#include "config.h"
 
 const char *find_hook(const char *name)
 {
@@ -40,3 +41,104 @@ int hook_exists(const char *name)
 {
 	return !!find_hook(name);
 }
+
+static int pick_next_hook(struct child_process *cp,
+			  struct strbuf *out,
+			  void *pp_cb,
+			  void **pp_task_cb)
+{
+	struct hook_cb_data *hook_cb = pp_cb;
+	const char *hook_path = hook_cb->hook_path;
+
+	if (!hook_path)
+		return 0;
+
+	cp->no_stdin = 1;
+	cp->env = hook_cb->options->env.v;
+	cp->stdout_to_stderr = 1;
+	cp->trace2_hook_name = hook_cb->hook_name;
+
+	strvec_push(&cp->args, hook_path);
+	strvec_pushv(&cp->args, hook_cb->options->args.v);
+
+	/* Provide context for errors if necessary */
+	*pp_task_cb = (char *)hook_path;
+
+	/*
+	 * This pick_next_hook() will be called again, we're only
+	 * running one hook, so indicate that no more work will be
+	 * done.
+	 */
+	hook_cb->hook_path = NULL;
+
+	return 1;
+}
+
+static int notify_start_failure(struct strbuf *out,
+				void *pp_cb,
+				void *pp_task_cp)
+{
+	struct hook_cb_data *hook_cb = pp_cb;
+	const char *hook_path = pp_task_cp;
+
+	hook_cb->rc |= 1;
+
+	strbuf_addf(out, _("Couldn't start hook '%s'\n"),
+		    hook_path);
+
+	return 1;
+}
+
+static int notify_hook_finished(int result,
+				struct strbuf *out,
+				void *pp_cb,
+				void *pp_task_cb)
+{
+	struct hook_cb_data *hook_cb = pp_cb;
+
+	hook_cb->rc |= result;
+
+	return 0;
+}
+
+static void run_hooks_opt_clear(struct run_hooks_opt *options)
+{
+	strvec_clear(&options->env);
+	strvec_clear(&options->args);
+}
+
+int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)
+{
+	struct hook_cb_data cb_data = {
+		.rc = 0,
+		.hook_name = hook_name,
+		.options = options,
+	};
+	const char *const hook_path = find_hook(hook_name);
+	int jobs = 1;
+	int ret = 0;
+
+	if (!options)
+		BUG("a struct run_hooks_opt must be provided to run_hooks");
+
+	if (!hook_path && !options->error_if_missing)
+		goto cleanup;
+
+	if (!hook_path) {
+		ret = error("cannot find a hook named %s", hook_name);
+		goto cleanup;
+	}
+
+	cb_data.hook_path = hook_path;
+	run_processes_parallel_tr2(jobs,
+				   pick_next_hook,
+				   notify_start_failure,
+				   notify_hook_finished,
+				   &cb_data,
+				   "hook",
+				   hook_name);
+	ret = cb_data.rc;
+cleanup:
+	run_hooks_opt_clear(options);
+	return ret;
+}
diff --git a/hook.h b/hook.h
index 6aa36fc7ff9..782385cc235 100644
--- a/hook.h
+++ b/hook.h
@@ -1,5 +1,31 @@
 #ifndef HOOK_H
 #define HOOK_H
+#include "strvec.h"
+
+struct run_hooks_opt
+{
+	/* Environment vars to be set for each hook */
+	struct strvec env;
+
+	/* Args to be passed to each hook */
+	struct strvec args;
+
+	/* Emit an error if the hook is missing */
+	unsigned int error_if_missing:1;
+};
+
+#define RUN_HOOKS_OPT_INIT { \
+	.env = STRVEC_INIT, \
+	.args = STRVEC_INIT, \
+}
+
+struct hook_cb_data {
+	/* rc reflects the cumulative failure state */
+	int rc;
+	const char *hook_name;
+	const char *hook_path;
+	struct run_hooks_opt *options;
+};
 
 /*
  * Returns the path to the hook file, or NULL if the hook is missing
@@ -13,4 +39,13 @@ const char *find_hook(const char *name);
  */
 int hook_exists(const char *hookname);
 
+/**
+ * Takes a `hook_name`, resolves it to a path with find_hook(), and
+ * runs the hook for you with the options specified in "struct
+ * run_hooks opt". Will free memory associated with the "struct run_hooks_opt".
+ *
+ * Returns the status code of the run hook, or a negative value on
+ * error().
+ */
+int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);
 #endif
diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh
new file mode 100755
index 00000000000..3aea1b105f0
--- /dev/null
+++ b/t/t1800-hook.sh
@@ -0,0 +1,129 @@
+#!/bin/sh
+
+test_description='git-hook command'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+
+test_expect_success 'git hook usage' '
+	test_expect_code 129 git hook &&
+	test_expect_code 129 git hook run &&
+	test_expect_code 129 git hook run -h &&
+	test_expect_code 129 git hook run --unknown 2>err &&
+	grep "unknown option" err
+'
+
+test_expect_success 'git hook run: nonexistent hook' '
+	cat >stderr.expect <<-\EOF &&
+	error: cannot find a hook named test-hook
+	EOF
+	test_expect_code 1 git hook run test-hook 2>stderr.actual &&
+	test_cmp stderr.expect stderr.actual
+'
+
+test_expect_success 'git hook run: basic' '
+	write_script .git/hooks/test-hook <<-EOF &&
+	echo Test hook
+	EOF
+
+	cat >expect <<-\EOF &&
+	Test hook
+	EOF
+	git hook run test-hook 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git hook run: stdout and stderr both write to our stderr' '
+	write_script .git/hooks/test-hook <<-EOF &&
+	echo >&1 Will end up on stderr
+	echo >&2 Will end up on stderr
+	EOF
+
+	cat >stderr.expect <<-\EOF &&
+	Will end up on stderr
+	Will end up on stderr
+	EOF
+	git hook run test-hook >stdout.actual 2>stderr.actual &&
+	test_cmp stderr.expect stderr.actual &&
+	test_must_be_empty stdout.actual
+'
+
+test_expect_success 'git hook run: exit codes are passed along' '
+	write_script .git/hooks/test-hook <<-EOF &&
+	exit 1
+	EOF
+
+	test_expect_code 1 git hook run test-hook &&
+
+	write_script .git/hooks/test-hook <<-EOF &&
+	exit 2
+	EOF
+
+	test_expect_code 2 git hook run test-hook &&
+
+	write_script .git/hooks/test-hook <<-EOF &&
+	exit 128
+	EOF
+
+	test_expect_code 128 git hook run test-hook &&
+
+	write_script .git/hooks/test-hook <<-EOF &&
+	exit 129
+	EOF
+
+	test_expect_code 129 git hook run test-hook
+'
+
+test_expect_success 'git hook run arg u ments without -- is not allowed' '
+	test_expect_code 129 git hook run test-hook arg u ments
+'
+
+test_expect_success 'git hook run -- pass arguments' '
+	write_script .git/hooks/test-hook <<-\EOF &&
+	echo $1
+	echo $2
+	EOF
+
+	cat >expect <<-EOF &&
+	arg
+	u ments
+	EOF
+
+	git hook run test-hook -- arg "u ments" 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git hook run -- out-of-repo runs excluded' '
+	write_script .git/hooks/test-hook <<-EOF &&
+	echo Test hook
+	EOF
+
+	nongit test_must_fail git hook run test-hook
+'
+
+test_expect_success 'git -c core.hooksPath=<PATH> hook run' '
+	mkdir my-hooks &&
+	write_script my-hooks/test-hook <<-\EOF &&
+	echo Hook ran $1 >>actual
+	EOF
+
+	cat >expect <<-\EOF &&
+	Test hook
+	Hook ran one
+	Hook ran two
+	Hook ran three
+	Hook ran four
+	EOF
+
+	# Test various ways of specifying the path. See also
+	# t1350-config-hooks-path.sh
+	>actual &&
+	git hook run test-hook -- ignored 2>>actual &&
+	git -c core.hooksPath=my-hooks hook run test-hook -- one 2>>actual &&
+	git -c core.hooksPath=my-hooks/ hook run test-hook -- two 2>>actual &&
+	git -c core.hooksPath="$PWD/my-hooks" hook run test-hook -- three 2>>actual &&
+	git -c core.hooksPath="$PWD/my-hooks/" hook run test-hook -- four 2>>actual &&
+	test_cmp expect actual
+'
+
+test_done
-- 
2.33.1.1570.g069344fdd45


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v4 02/17] hook API: add a run_hooks() wrapper
  2021-11-01 18:56   ` [PATCH v4 00/17] " Ævar Arnfjörð Bjarmason
  2021-11-01 18:56     ` [PATCH v4 01/17] hook: add 'run' subcommand Ævar Arnfjörð Bjarmason
@ 2021-11-01 18:56     ` Ævar Arnfjörð Bjarmason
  2021-11-01 18:56     ` [PATCH v4 03/17] gc: use hook library for pre-auto-gc hook Ævar Arnfjörð Bjarmason
                       ` (15 subsequent siblings)
  17 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-01 18:56 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

Add a run_hooks() wrapper, we'll use it in subsequent commits for the
simple cases of wanting to run a single hook under a given name,
without providing options such as "env" or "args".

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 hook.c | 7 +++++++
 hook.h | 6 ++++++
 2 files changed, 13 insertions(+)

diff --git a/hook.c b/hook.c
index 64d3608e45e..720994bf4f6 100644
--- a/hook.c
+++ b/hook.c
@@ -142,3 +142,10 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)
 	run_hooks_opt_clear(options);
 	return ret;
 }
+
+int run_hooks(const char *hook_name)
+{
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+
+	return run_hooks_opt(hook_name, &opt);
+}
diff --git a/hook.h b/hook.h
index 782385cc235..9c358789958 100644
--- a/hook.h
+++ b/hook.h
@@ -48,4 +48,10 @@ int hook_exists(const char *hookname);
  * error().
  */
 int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);
+
+/**
+ * A wrapper for run_hooks_opt() which provides a dummy "struct
+ * run_hooks_opt" initialized with "RUN_HOOKS_OPT_INIT".
+ */
+int run_hooks(const char *hook_name);
 #endif
-- 
2.33.1.1570.g069344fdd45


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v4 03/17] gc: use hook library for pre-auto-gc hook
  2021-11-01 18:56   ` [PATCH v4 00/17] " Ævar Arnfjörð Bjarmason
  2021-11-01 18:56     ` [PATCH v4 01/17] hook: add 'run' subcommand Ævar Arnfjörð Bjarmason
  2021-11-01 18:56     ` [PATCH v4 02/17] hook API: add a run_hooks() wrapper Ævar Arnfjörð Bjarmason
@ 2021-11-01 18:56     ` Ævar Arnfjörð Bjarmason
  2021-11-01 18:56     ` [PATCH v4 04/17] am: convert {pre,post}-applypatch to use hook.h Ævar Arnfjörð Bjarmason
                       ` (14 subsequent siblings)
  17 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-01 18:56 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the pre-auto-gc hook away from run-command.h to and over to the
new hook.h library. This uses the new run_hooks() wrapper.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/gc.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/builtin/gc.c b/builtin/gc.c
index 6b3de3dd514..f57cbd2d217 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -32,6 +32,7 @@
 #include "remote.h"
 #include "object-store.h"
 #include "exec-cmd.h"
+#include "hook.h"
 
 #define FAILED_RUN "failed to run %s"
 
@@ -394,7 +395,7 @@ static int need_to_gc(void)
 	else
 		return 0;
 
-	if (run_hook_le(NULL, "pre-auto-gc", NULL))
+	if (run_hooks("pre-auto-gc"))
 		return 0;
 	return 1;
 }
-- 
2.33.1.1570.g069344fdd45


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v4 04/17] am: convert {pre,post}-applypatch to use hook.h
  2021-11-01 18:56   ` [PATCH v4 00/17] " Ævar Arnfjörð Bjarmason
                       ` (2 preceding siblings ...)
  2021-11-01 18:56     ` [PATCH v4 03/17] gc: use hook library for pre-auto-gc hook Ævar Arnfjörð Bjarmason
@ 2021-11-01 18:56     ` Ævar Arnfjörð Bjarmason
  2021-11-01 18:56     ` [PATCH v4 05/17] hook API: add a run_hooks_l() wrapper Ævar Arnfjörð Bjarmason
                       ` (13 subsequent siblings)
  17 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-01 18:56 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Teach pre-applypatch and post-applypatch to use the hook.h library
instead of the run-command.h library.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/am.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..4b334cb7b12 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1609,7 +1609,7 @@ static void do_commit(const struct am_state *state)
 	const char *reflog_msg, *author, *committer = NULL;
 	struct strbuf sb = STRBUF_INIT;
 
-	if (run_hook_le(NULL, "pre-applypatch", NULL))
+	if (run_hooks("pre-applypatch"))
 		exit(1);
 
 	if (write_cache_as_tree(&tree, 0, NULL))
@@ -1661,7 +1661,7 @@ static void do_commit(const struct am_state *state)
 		fclose(fp);
 	}
 
-	run_hook_le(NULL, "post-applypatch", NULL);
+	run_hooks("post-applypatch");
 
 	strbuf_release(&sb);
 }
-- 
2.33.1.1570.g069344fdd45


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v4 05/17] hook API: add a run_hooks_l() wrapper
  2021-11-01 18:56   ` [PATCH v4 00/17] " Ævar Arnfjörð Bjarmason
                       ` (3 preceding siblings ...)
  2021-11-01 18:56     ` [PATCH v4 04/17] am: convert {pre,post}-applypatch to use hook.h Ævar Arnfjörð Bjarmason
@ 2021-11-01 18:56     ` Ævar Arnfjörð Bjarmason
  2021-11-01 18:56     ` [PATCH v4 06/17] rebase: convert pre-rebase to use hook.h Ævar Arnfjörð Bjarmason
                       ` (12 subsequent siblings)
  17 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-01 18:56 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

Add a run_hooks_l() wrapper, we'll use it in subsequent commits for
the simple cases of wanting to run a single hook under a given name
along with a list of arguments.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 hook.c | 14 ++++++++++++++
 hook.h | 10 ++++++++++
 2 files changed, 24 insertions(+)

diff --git a/hook.c b/hook.c
index 720994bf4f6..cf6d099f83a 100644
--- a/hook.c
+++ b/hook.c
@@ -149,3 +149,17 @@ int run_hooks(const char *hook_name)
 
 	return run_hooks_opt(hook_name, &opt);
 }
+
+int run_hooks_l(const char *hook_name, ...)
+{
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+	va_list ap;
+	const char *arg;
+
+	va_start(ap, hook_name);
+	while ((arg = va_arg(ap, const char *)))
+		strvec_push(&opt.args, arg);
+	va_end(ap);
+
+	return run_hooks_opt(hook_name, &opt);
+}
diff --git a/hook.h b/hook.h
index 9c358789958..54528395953 100644
--- a/hook.h
+++ b/hook.h
@@ -54,4 +54,14 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);
  * run_hooks_opt" initialized with "RUN_HOOKS_OPT_INIT".
  */
 int run_hooks(const char *hook_name);
+
+/**
+ * Like run_hooks(), a wrapper for run_hooks_opt().
+ *
+ * In addition to the wrapping behavior provided by run_hooks(), this
+ * wrapper takes a list of strings terminated by a NULL
+ * argument. These things will be used as positional arguments to the
+ * hook. This function behaves like the old run_hook_le() API.
+ */
+int run_hooks_l(const char *hook_name, ...);
 #endif
-- 
2.33.1.1570.g069344fdd45


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v4 06/17] rebase: convert pre-rebase to use hook.h
  2021-11-01 18:56   ` [PATCH v4 00/17] " Ævar Arnfjörð Bjarmason
                       ` (4 preceding siblings ...)
  2021-11-01 18:56     ` [PATCH v4 05/17] hook API: add a run_hooks_l() wrapper Ævar Arnfjörð Bjarmason
@ 2021-11-01 18:56     ` Ævar Arnfjörð Bjarmason
  2021-11-01 18:56     ` [PATCH v4 07/17] am: convert applypatch-msg " Ævar Arnfjörð Bjarmason
                       ` (11 subsequent siblings)
  17 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-01 18:56 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the pre-rebase hook away from run-command.h to and over to the
new hook.h library.

Since this hook needs arguments introduce a run_hooksl() wrapper, like
run_hooks(), but it takes varargs.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/rebase.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 34b4744e5f3..ac4120013a0 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -28,6 +28,7 @@
 #include "sequencer.h"
 #include "rebase-interactive.h"
 #include "reset.h"
+#include "hook.h"
 
 #define DEFAULT_REFLOG_ACTION "rebase"
 
@@ -1712,7 +1713,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
 	/* If a hook exists, give it a chance to interrupt*/
 	if (!ok_to_skip_pre_rebase &&
-	    run_hook_le(NULL, "pre-rebase", options.upstream_arg,
+	    run_hooks_l("pre-rebase", options.upstream_arg,
 			argc ? argv[0] : NULL, NULL))
 		die(_("The pre-rebase hook refused to rebase."));
 
-- 
2.33.1.1570.g069344fdd45


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v4 07/17] am: convert applypatch-msg to use hook.h
  2021-11-01 18:56   ` [PATCH v4 00/17] " Ævar Arnfjörð Bjarmason
                       ` (5 preceding siblings ...)
  2021-11-01 18:56     ` [PATCH v4 06/17] rebase: convert pre-rebase to use hook.h Ævar Arnfjörð Bjarmason
@ 2021-11-01 18:56     ` Ævar Arnfjörð Bjarmason
  2021-11-01 18:56     ` [PATCH v4 08/17] merge: convert post-merge " Ævar Arnfjörð Bjarmason
                       ` (10 subsequent siblings)
  17 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-01 18:56 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Teach applypatch-msg to use the hook.h library instead of the
run-command.h library.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/am.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/am.c b/builtin/am.c
index 4b334cb7b12..ae0c484dcba 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -448,7 +448,7 @@ static int run_applypatch_msg_hook(struct am_state *state)
 	int ret;
 
 	assert(state->msg);
-	ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL);
+	ret = run_hooks_l("applypatch-msg", am_path(state, "final-commit"), NULL);
 
 	if (!ret) {
 		FREE_AND_NULL(state->msg);
-- 
2.33.1.1570.g069344fdd45


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v4 08/17] merge: convert post-merge to use hook.h
  2021-11-01 18:56   ` [PATCH v4 00/17] " Ævar Arnfjörð Bjarmason
                       ` (6 preceding siblings ...)
  2021-11-01 18:56     ` [PATCH v4 07/17] am: convert applypatch-msg " Ævar Arnfjörð Bjarmason
@ 2021-11-01 18:56     ` Ævar Arnfjörð Bjarmason
  2021-11-01 18:56     ` [PATCH v4 09/17] hooks: convert non-worktree 'post-checkout' hook to hook library Ævar Arnfjörð Bjarmason
                       ` (9 subsequent siblings)
  17 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-01 18:56 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Teach post-merge to use the hook.h library instead of the
run-command.h library to run hooks.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/merge.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/merge.c b/builtin/merge.c
index ea3112e0c0b..e6facd1c95d 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -488,7 +488,7 @@ static void finish(struct commit *head_commit,
 	}
 
 	/* Run a post-merge hook */
-	run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL);
+	run_hooks_l("post-merge", squash ? "1" : "0", NULL);
 
 	apply_autostash(git_path_merge_autostash(the_repository));
 	strbuf_release(&reflog_message);
-- 
2.33.1.1570.g069344fdd45


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v4 09/17] hooks: convert non-worktree 'post-checkout' hook to hook library
  2021-11-01 18:56   ` [PATCH v4 00/17] " Ævar Arnfjörð Bjarmason
                       ` (7 preceding siblings ...)
  2021-11-01 18:56     ` [PATCH v4 08/17] merge: convert post-merge " Ævar Arnfjörð Bjarmason
@ 2021-11-01 18:56     ` Ævar Arnfjörð Bjarmason
  2021-11-01 18:56     ` [PATCH v4 10/17] hooks: convert worktree " Ævar Arnfjörð Bjarmason
                       ` (8 subsequent siblings)
  17 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-01 18:56 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the running of the 'post-checkout' hook away from run-command.h
to the new hook.h library, except in the case of
builtin/worktree.c. That special-case will be handled in a subsequent
commit.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/checkout.c | 3 ++-
 builtin/clone.c    | 3 ++-
 reset.c            | 3 ++-
 3 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index cbf73b8c9f6..4af17d17217 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -9,6 +9,7 @@
 #include "config.h"
 #include "diff.h"
 #include "dir.h"
+#include "hook.h"
 #include "ll-merge.h"
 #include "lockfile.h"
 #include "merge-recursive.h"
@@ -106,7 +107,7 @@ struct branch_info {
 static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,
 			      int changed)
 {
-	return run_hook_le(NULL, "post-checkout",
+	return run_hooks_l("post-checkout",
 			   oid_to_hex(old_commit ? &old_commit->object.oid : null_oid()),
 			   oid_to_hex(new_commit ? &new_commit->object.oid : null_oid()),
 			   changed ? "1" : "0", NULL);
diff --git a/builtin/clone.c b/builtin/clone.c
index fb377b27657..ee27b9f8114 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -32,6 +32,7 @@
 #include "connected.h"
 #include "packfile.h"
 #include "list-objects-filter-options.h"
+#include "hook.h"
 
 /*
  * Overall FIXMEs:
@@ -705,7 +706,7 @@ static int checkout(int submodule_progress)
 	if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
 		die(_("unable to write new index file"));
 
-	err |= run_hook_le(NULL, "post-checkout", oid_to_hex(null_oid()),
+	err |= run_hooks_l("post-checkout", oid_to_hex(null_oid()),
 			   oid_to_hex(&oid), "1", NULL);
 
 	if (!err && (option_recurse_submodules.nr > 0)) {
diff --git a/reset.c b/reset.c
index f214df3d96c..0881e636915 100644
--- a/reset.c
+++ b/reset.c
@@ -7,6 +7,7 @@
 #include "tree-walk.h"
 #include "tree.h"
 #include "unpack-trees.h"
+#include "hook.h"
 
 int reset_head(struct repository *r, struct object_id *oid, const char *action,
 	       const char *switch_to_branch, unsigned flags,
@@ -127,7 +128,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,
 					    reflog_head);
 	}
 	if (run_hook)
-		run_hook_le(NULL, "post-checkout",
+		run_hooks_l("post-checkout",
 			    oid_to_hex(orig ? orig : null_oid()),
 			    oid_to_hex(oid), "1", NULL);
 
-- 
2.33.1.1570.g069344fdd45


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v4 10/17] hooks: convert worktree 'post-checkout' hook to hook library
  2021-11-01 18:56   ` [PATCH v4 00/17] " Ævar Arnfjörð Bjarmason
                       ` (8 preceding siblings ...)
  2021-11-01 18:56     ` [PATCH v4 09/17] hooks: convert non-worktree 'post-checkout' hook to hook library Ævar Arnfjörð Bjarmason
@ 2021-11-01 18:56     ` Ævar Arnfjörð Bjarmason
  2021-11-01 18:56     ` [PATCH v4 11/17] git hook run: add an --ignore-missing flag Ævar Arnfjörð Bjarmason
                       ` (7 subsequent siblings)
  17 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-01 18:56 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the running of the 'post-checkout' hook away from run-command.h
to the new hook.h library in builtin/worktree.c. For this special case
we need a change to the hook API to teach it to run the hook from a
given directory.

We cannot skip the "absolute_path" flag and just check if "dir" is
specified as we'd then fail to find our hook in the new dir we'd
chdir() to. We currently don't have a use-case for running a hook not
in our "base" repository at a given absolute path, so let's have "dir"
imply absolute_path(find_hook(hook_name)).

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/worktree.c | 27 +++++++++++----------------
 hook.c             |  8 ++++++++
 hook.h             |  6 ++++++
 3 files changed, 25 insertions(+), 16 deletions(-)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index d22ece93e1a..4d9df5ecc4c 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -382,22 +382,17 @@ static int add_worktree(const char *path, const char *refname,
 	 * is_junk is cleared, but do return appropriate code when hook fails.
 	 */
 	if (!ret && opts->checkout) {
-		const char *hook = find_hook("post-checkout");
-		if (hook) {
-			const char *env[] = { "GIT_DIR", "GIT_WORK_TREE", NULL };
-			cp.git_cmd = 0;
-			cp.no_stdin = 1;
-			cp.stdout_to_stderr = 1;
-			cp.dir = path;
-			cp.env = env;
-			cp.argv = NULL;
-			cp.trace2_hook_name = "post-checkout";
-			strvec_pushl(&cp.args, absolute_path(hook),
-				     oid_to_hex(null_oid()),
-				     oid_to_hex(&commit->object.oid),
-				     "1", NULL);
-			ret = run_command(&cp);
-		}
+		struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+
+		strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL);
+		strvec_pushl(&opt.args,
+			     oid_to_hex(null_oid()),
+			     oid_to_hex(&commit->object.oid),
+			     "1",
+			     NULL);
+		opt.dir = path;
+
+		ret = run_hooks_opt("post-checkout", &opt);
 	}
 
 	strvec_clear(&child_env);
diff --git a/hook.c b/hook.c
index cf6d099f83a..95f5b926354 100644
--- a/hook.c
+++ b/hook.c
@@ -57,6 +57,7 @@ static int pick_next_hook(struct child_process *cp,
 	cp->env = hook_cb->options->env.v;
 	cp->stdout_to_stderr = 1;
 	cp->trace2_hook_name = hook_cb->hook_name;
+	cp->dir = hook_cb->options->dir;
 
 	strvec_push(&cp->args, hook_path);
 	strvec_pushv(&cp->args, hook_cb->options->args.v);
@@ -109,6 +110,7 @@ static void run_hooks_opt_clear(struct run_hooks_opt *options)
 
 int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)
 {
+	struct strbuf abs_path = STRBUF_INIT;
 	struct hook_cb_data cb_data = {
 		.rc = 0,
 		.hook_name = hook_name,
@@ -130,6 +132,11 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)
 	}
 
 	cb_data.hook_path = hook_path;
+	if (options->dir) {
+		strbuf_add_absolute_path(&abs_path, hook_path);
+		cb_data.hook_path = abs_path.buf;
+	}
+
 	run_processes_parallel_tr2(jobs,
 				   pick_next_hook,
 				   notify_start_failure,
@@ -139,6 +146,7 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)
 				   hook_name);
 	ret = cb_data.rc;
 cleanup:
+	strbuf_release(&abs_path);
 	run_hooks_opt_clear(options);
 	return ret;
 }
diff --git a/hook.h b/hook.h
index 54528395953..18d90aedf14 100644
--- a/hook.h
+++ b/hook.h
@@ -12,6 +12,12 @@ struct run_hooks_opt
 
 	/* Emit an error if the hook is missing */
 	unsigned int error_if_missing:1;
+
+	/**
+	 * An optional initial working directory for the hook,
+	 * translates to "struct child_process"'s "dir" member.
+	 */
+	const char *dir;
 };
 
 #define RUN_HOOKS_OPT_INIT { \
-- 
2.33.1.1570.g069344fdd45


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v4 11/17] git hook run: add an --ignore-missing flag
  2021-11-01 18:56   ` [PATCH v4 00/17] " Ævar Arnfjörð Bjarmason
                       ` (9 preceding siblings ...)
  2021-11-01 18:56     ` [PATCH v4 10/17] hooks: convert worktree " Ævar Arnfjörð Bjarmason
@ 2021-11-01 18:56     ` Ævar Arnfjörð Bjarmason
  2021-11-01 18:56     ` [PATCH v4 12/17] send-email: use 'git hook run' for 'sendemail-validate' Ævar Arnfjörð Bjarmason
                       ` (6 subsequent siblings)
  17 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-01 18:56 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

For certain one-shot hooks we'd like to optimistically run them, and
not complain if they don't exist.

This was already supported by the underlying hook.c library, but had
not been exposed via "git hook run". The command version of this will
be used by send-email in a subsequent commit.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 Documentation/git-hook.txt | 10 +++++++++-
 builtin/hook.c             |  8 ++++++--
 t/t1800-hook.sh            |  5 +++++
 3 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt
index e39b1b5d069..77c3a8ad909 100644
--- a/Documentation/git-hook.txt
+++ b/Documentation/git-hook.txt
@@ -8,7 +8,7 @@ git-hook - Run git hooks
 SYNOPSIS
 --------
 [verse]
-'git hook' run <hook-name> [-- <hook-args>]
+'git hook' run [--ignore-missing] <hook-name> [-- <hook-args>]
 
 DESCRIPTION
 -----------
@@ -28,6 +28,14 @@ Any positional arguments to the hook should be passed after a
 mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See
 linkgit:githooks[5] for arguments hooks might expect (if any).
 
+OPTIONS
+-------
+
+--ignore-missing::
+	Ignore any missing hook by quietly returning zero. Used for
+	tools that want to do a blind one-shot run of a hook that may
+	or may not be present.
+
 SEE ALSO
 --------
 linkgit:githooks[5]
diff --git a/builtin/hook.c b/builtin/hook.c
index 9b67ff50cef..54e5c6ec933 100644
--- a/builtin/hook.c
+++ b/builtin/hook.c
@@ -7,7 +7,7 @@
 #include "strvec.h"
 
 #define BUILTIN_HOOK_RUN_USAGE \
-	N_("git hook run <hook-name> [-- <hook-args>]")
+	N_("git hook run [--ignore-missing] <hook-name> [-- <hook-args>]")
 
 static const char * const builtin_hook_usage[] = {
 	BUILTIN_HOOK_RUN_USAGE,
@@ -23,8 +23,11 @@ static int run(int argc, const char **argv, const char *prefix)
 {
 	int i;
 	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+	int ignore_missing = 0;
 	const char *hook_name;
 	struct option run_options[] = {
+		OPT_BOOL(0, "ignore-missing", &ignore_missing,
+			 N_("silently ignore missing requested <hook-name>")),
 		OPT_END(),
 	};
 	int ret;
@@ -52,7 +55,8 @@ static int run(int argc, const char **argv, const char *prefix)
 	git_config(git_default_config, NULL);
 
 	hook_name = argv[0];
-	opt.error_if_missing = 1;
+	if (!ignore_missing)
+		opt.error_if_missing = 1;
 	ret = run_hooks_opt(hook_name, &opt);
 	if (ret < 0) /* error() return */
 		ret = 1;
diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh
index 3aea1b105f0..29718aa9913 100755
--- a/t/t1800-hook.sh
+++ b/t/t1800-hook.sh
@@ -21,6 +21,11 @@ test_expect_success 'git hook run: nonexistent hook' '
 	test_cmp stderr.expect stderr.actual
 '
 
+test_expect_success 'git hook run: nonexistent hook with --ignore-missing' '
+	git hook run --ignore-missing does-not-exist 2>stderr.actual &&
+	test_must_be_empty stderr.actual
+'
+
 test_expect_success 'git hook run: basic' '
 	write_script .git/hooks/test-hook <<-EOF &&
 	echo Test hook
-- 
2.33.1.1570.g069344fdd45


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v4 12/17] send-email: use 'git hook run' for 'sendemail-validate'
  2021-11-01 18:56   ` [PATCH v4 00/17] " Ævar Arnfjörð Bjarmason
                       ` (10 preceding siblings ...)
  2021-11-01 18:56     ` [PATCH v4 11/17] git hook run: add an --ignore-missing flag Ævar Arnfjörð Bjarmason
@ 2021-11-01 18:56     ` Ævar Arnfjörð Bjarmason
  2021-11-01 18:56     ` [PATCH v4 13/17] git-p4: use 'git hook' to run hooks Ævar Arnfjörð Bjarmason
                       ` (5 subsequent siblings)
  17 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-01 18:56 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Change the "sendmail-validate" hook to be run via the "git hook run"
wrapper instead of via a direct invocation.

This is the smallest possibly change to get "send-email" using "git
hook run". We still check the hook itself with "-x", and set a
"GIT_DIR" variable, both of which are asserted by our tests. We'll
need to get rid of this special behavior if we start running N hooks,
but for now let's be as close to bug-for-bug compatible as possible.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 git-send-email.perl   | 22 ++++++++++++++--------
 t/t9001-send-email.sh |  4 ++--
 2 files changed, 16 insertions(+), 10 deletions(-)

diff --git a/git-send-email.perl b/git-send-email.perl
index 5262d88ee32..4c20c0bbb79 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -195,13 +195,13 @@ sub format_2822_time {
 my $editor;
 
 sub system_or_msg {
-	my ($args, $msg) = @_;
+	my ($args, $msg, $cmd_name) = @_;
 	system(@$args);
 	my $signalled = $? & 127;
 	my $exit_code = $? >> 8;
 	return unless $signalled or $exit_code;
 
-	my @sprintf_args = ($args->[0], $exit_code);
+	my @sprintf_args = ($cmd_name ? $cmd_name : $args->[0], $exit_code);
 	if (defined $msg) {
 		# Quiet the 'redundant' warning category, except we
 		# need to support down to Perl 5.8, so we can't do a
@@ -2039,10 +2039,10 @@ sub validate_patch {
 	my ($fn, $xfer_encoding) = @_;
 
 	if ($repo) {
+		my $hook_name = 'sendemail-validate';
 		my $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks');
 		require File::Spec;
-		my $validate_hook = File::Spec->catfile($hooks_path,
-					    'sendemail-validate');
+		my $validate_hook = File::Spec->catfile($hooks_path, $hook_name);
 		my $hook_error;
 		if (-x $validate_hook) {
 			require Cwd;
@@ -2052,13 +2052,19 @@ sub validate_patch {
 			chdir($repo->wc_path() or $repo->repo_path())
 				or die("chdir: $!");
 			local $ENV{"GIT_DIR"} = $repo->repo_path();
-			$hook_error = system_or_msg([$validate_hook, $target]);
+			my @cmd = ("git", "hook", "run", "--ignore-missing",
+				    $hook_name, "--");
+			my @cmd_msg = (@cmd, "<patch>");
+			my @cmd_run = (@cmd, $target);
+			$hook_error = system_or_msg(\@cmd_run, undef, "@cmd_msg");
 			chdir($cwd_save) or die("chdir: $!");
 		}
 		if ($hook_error) {
-			die sprintf(__("fatal: %s: rejected by sendemail-validate hook\n" .
-				       "%s\n" .
-				       "warning: no patches were sent\n"), $fn, $hook_error);
+			$hook_error = sprintf(__("fatal: %s: rejected by %s hook\n" .
+						 $hook_error . "\n" .
+						 "warning: no patches were sent\n"),
+					      $fn, $hook_name);
+			die $hook_error;
 		}
 	}
 
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index aa0c20499ba..84d0f40d76a 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -539,7 +539,7 @@ test_expect_success $PREREQ "--validate respects relative core.hooksPath path" '
 	test_path_is_file my-hooks.ran &&
 	cat >expect <<-EOF &&
 	fatal: longline.patch: rejected by sendemail-validate hook
-	fatal: command '"'"'my-hooks/sendemail-validate'"'"' died with exit code 1
+	fatal: command '"'"'git hook run --ignore-missing sendemail-validate -- <patch>'"'"' died with exit code 1
 	warning: no patches were sent
 	EOF
 	test_cmp expect actual
@@ -558,7 +558,7 @@ test_expect_success $PREREQ "--validate respects absolute core.hooksPath path" '
 	test_path_is_file my-hooks.ran &&
 	cat >expect <<-EOF &&
 	fatal: longline.patch: rejected by sendemail-validate hook
-	fatal: command '"'"'$hooks_path/sendemail-validate'"'"' died with exit code 1
+	fatal: command '"'"'git hook run --ignore-missing sendemail-validate -- <patch>'"'"' died with exit code 1
 	warning: no patches were sent
 	EOF
 	test_cmp expect actual
-- 
2.33.1.1570.g069344fdd45


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v4 13/17] git-p4: use 'git hook' to run hooks
  2021-11-01 18:56   ` [PATCH v4 00/17] " Ævar Arnfjörð Bjarmason
                       ` (11 preceding siblings ...)
  2021-11-01 18:56     ` [PATCH v4 12/17] send-email: use 'git hook run' for 'sendemail-validate' Ævar Arnfjörð Bjarmason
@ 2021-11-01 18:56     ` Ævar Arnfjörð Bjarmason
  2021-11-01 18:56     ` [PATCH v4 14/17] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h Ævar Arnfjörð Bjarmason
                       ` (4 subsequent siblings)
  17 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-01 18:56 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Instead of duplicating the behavior of run-command.h:run_hook_le() in
Python, we can directly call 'git hook run'. We emulate the existence
check with the --ignore-missing flag.

We're dropping the "verbose" handling added in 9f59ca4d6af (git-p4:
create new function run_git_hook, 2020-02-11), those who want
diagnostic output about how hooks are run are now able to get that via
e.g. the trace2 facility and GIT_TRACE=1.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 git-p4.py | 70 +++++--------------------------------------------------
 1 file changed, 6 insertions(+), 64 deletions(-)

diff --git a/git-p4.py b/git-p4.py
index 2b4500226aa..3b54168eb4a 100755
--- a/git-p4.py
+++ b/git-p4.py
@@ -208,70 +208,12 @@ def decode_path(path):
 
 def run_git_hook(cmd, param=[]):
     """Execute a hook if the hook exists."""
-    if verbose:
-        sys.stderr.write("Looking for hook: %s\n" % cmd)
-        sys.stderr.flush()
-
-    hooks_path = gitConfig("core.hooksPath")
-    if len(hooks_path) <= 0:
-        hooks_path = os.path.join(os.environ["GIT_DIR"], "hooks")
-
-    if not isinstance(param, list):
-        param=[param]
-
-    # resolve hook file name, OS depdenent
-    hook_file = os.path.join(hooks_path, cmd)
-    if platform.system() == 'Windows':
-        if not os.path.isfile(hook_file):
-            # look for the file with an extension
-            files = glob.glob(hook_file + ".*")
-            if not files:
-                return True
-            files.sort()
-            hook_file = files.pop()
-            while hook_file.upper().endswith(".SAMPLE"):
-                # The file is a sample hook. We don't want it
-                if len(files) > 0:
-                    hook_file = files.pop()
-                else:
-                    return True
-
-    if not os.path.isfile(hook_file) or not os.access(hook_file, os.X_OK):
-        return True
-
-    return run_hook_command(hook_file, param) == 0
-
-def run_hook_command(cmd, param):
-    """Executes a git hook command
-       cmd = the command line file to be executed. This can be
-       a file that is run by OS association.
-
-       param = a list of parameters to pass to the cmd command
-
-       On windows, the extension is checked to see if it should
-       be run with the Git for Windows Bash shell.  If there
-       is no file extension, the file is deemed a bash shell
-       and will be handed off to sh.exe. Otherwise, Windows
-       will be called with the shell to handle the file assocation.
-
-       For non Windows operating systems, the file is called
-       as an executable.
-    """
-    cli = [cmd] + param
-    use_shell = False
-    if platform.system() == 'Windows':
-        (root,ext) = os.path.splitext(cmd)
-        if ext == "":
-            exe_path = os.environ.get("EXEPATH")
-            if exe_path is None:
-                exe_path = ""
-            else:
-                exe_path = os.path.join(exe_path, "bin")
-            cli = [os.path.join(exe_path, "SH.EXE")] + cli
-        else:
-            use_shell = True
-    return subprocess.call(cli, shell=use_shell)
-
+    args = ['git', 'hook', 'run', '--ignore-missing', cmd]
+    if param:
+        args.append("--")
+        for p in param:
+            args.append(p)
+    return subprocess.call(args) == 0
 
 def write_pipe(c, stdin):
     if verbose:
-- 
2.33.1.1570.g069344fdd45


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v4 14/17] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h
  2021-11-01 18:56   ` [PATCH v4 00/17] " Ævar Arnfjörð Bjarmason
                       ` (12 preceding siblings ...)
  2021-11-01 18:56     ` [PATCH v4 13/17] git-p4: use 'git hook' to run hooks Ævar Arnfjörð Bjarmason
@ 2021-11-01 18:56     ` Ævar Arnfjörð Bjarmason
  2021-11-01 18:56     ` [PATCH v4 15/17] read-cache: convert post-index-change to use hook.h Ævar Arnfjörð Bjarmason
                       ` (3 subsequent siblings)
  17 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-01 18:56 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move these hooks hook away from run-command.h to and over to the new
hook.h library.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 commit.c | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/commit.c b/commit.c
index 551de4903c1..581d7dc216c 100644
--- a/commit.c
+++ b/commit.c
@@ -21,6 +21,7 @@
 #include "commit-reach.h"
 #include "run-command.h"
 #include "shallow.h"
+#include "hook.h"
 
 static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
 
@@ -1700,22 +1701,22 @@ size_t ignore_non_trailer(const char *buf, size_t len)
 int run_commit_hook(int editor_is_used, const char *index_file,
 		    const char *name, ...)
 {
-	struct strvec hook_env = STRVEC_INIT;
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
 	va_list args;
-	int ret;
+	const char *arg;
 
-	strvec_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file);
+	strvec_pushf(&opt.env, "GIT_INDEX_FILE=%s", index_file);
 
 	/*
 	 * Let the hook know that no editor will be launched.
 	 */
 	if (!editor_is_used)
-		strvec_push(&hook_env, "GIT_EDITOR=:");
+		strvec_push(&opt.env, "GIT_EDITOR=:");
 
 	va_start(args, name);
-	ret = run_hook_ve(hook_env.v, name, args);
+	while ((arg = va_arg(args, const char *)))
+		strvec_push(&opt.args, arg);
 	va_end(args);
-	strvec_clear(&hook_env);
 
-	return ret;
+	return run_hooks_opt(name, &opt);
 }
-- 
2.33.1.1570.g069344fdd45


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v4 15/17] read-cache: convert post-index-change to use hook.h
  2021-11-01 18:56   ` [PATCH v4 00/17] " Ævar Arnfjörð Bjarmason
                       ` (13 preceding siblings ...)
  2021-11-01 18:56     ` [PATCH v4 14/17] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h Ævar Arnfjörð Bjarmason
@ 2021-11-01 18:56     ` Ævar Arnfjörð Bjarmason
  2021-11-01 18:56     ` [PATCH v4 16/17] receive-pack: convert push-to-checkout hook to hook.h Ævar Arnfjörð Bjarmason
                       ` (2 subsequent siblings)
  17 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-01 18:56 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the post-index-change hook away from run-command.h to and over to
the new hook.h library.

This removes the last direct user of "run_hook_ve()" outside of
run-command.c ("run_hook_le()" still uses it). So we can make the
function static now. A subsequent commit will remove this code
entirely when "run_hook_le()" itself goes away.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 read-cache.c  | 3 ++-
 run-command.c | 2 +-
 run-command.h | 1 -
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/read-cache.c b/read-cache.c
index f3986596623..4352661549f 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -28,6 +28,7 @@
 #include "sparse-index.h"
 #include "csum-file.h"
 #include "promisor-remote.h"
+#include "hook.h"
 
 /* Mask for the name length in ce_flags in the on-disk index */
 
@@ -3129,7 +3130,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
 	else
 		ret = close_lock_file_gently(lock);
 
-	run_hook_le(NULL, "post-index-change",
+	run_hooks_l("post-index-change",
 			istate->updated_workdir ? "1" : "0",
 			istate->updated_skipworktree ? "1" : "0", NULL);
 	istate->updated_workdir = 0;
diff --git a/run-command.c b/run-command.c
index 7ef5cc712a9..d92e670c8ed 100644
--- a/run-command.c
+++ b/run-command.c
@@ -1323,7 +1323,7 @@ int async_with_fork(void)
 #endif
 }
 
-int run_hook_ve(const char *const *env, const char *name, va_list args)
+static int run_hook_ve(const char *const *env, const char *name, va_list args)
 {
 	struct child_process hook = CHILD_PROCESS_INIT;
 	const char *p;
diff --git a/run-command.h b/run-command.h
index 49878262584..3fa7454cf8a 100644
--- a/run-command.h
+++ b/run-command.h
@@ -239,7 +239,6 @@ int run_command(struct child_process *);
  */
 LAST_ARG_MUST_BE_NULL
 int run_hook_le(const char *const *env, const char *name, ...);
-int run_hook_ve(const char *const *env, const char *name, va_list args);
 
 /*
  * Trigger an auto-gc
-- 
2.33.1.1570.g069344fdd45


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v4 16/17] receive-pack: convert push-to-checkout hook to hook.h
  2021-11-01 18:56   ` [PATCH v4 00/17] " Ævar Arnfjörð Bjarmason
                       ` (14 preceding siblings ...)
  2021-11-01 18:56     ` [PATCH v4 15/17] read-cache: convert post-index-change to use hook.h Ævar Arnfjörð Bjarmason
@ 2021-11-01 18:56     ` Ævar Arnfjörð Bjarmason
  2021-11-01 18:56     ` [PATCH v4 17/17] run-command: remove old run_hook_{le,ve}() hook API Ævar Arnfjörð Bjarmason
  2021-11-23 11:45     ` [PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
  17 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-01 18:56 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the push-to-checkout hook away from run-command.h to and over to
the new hook.h library.

This removes the last direct user of run_hook_le(), so we could remove
that function now, but let's leave that to a follow-up cleanup commit.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/receive-pack.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 49b846d9605..5d6d8dd9a26 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1439,9 +1439,12 @@ static const char *push_to_checkout(unsigned char *hash,
 				    struct strvec *env,
 				    const char *work_tree)
 {
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+
 	strvec_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree));
-	if (run_hook_le(env->v, push_to_checkout_hook,
-			hash_to_hex(hash), NULL))
+	strvec_pushv(&opt.env, env->v);
+	strvec_push(&opt.args, hash_to_hex(hash));
+	if (run_hooks_opt(push_to_checkout_hook, &opt))
 		return "push-to-checkout hook declined";
 	else
 		return NULL;
-- 
2.33.1.1570.g069344fdd45


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v4 17/17] run-command: remove old run_hook_{le,ve}() hook API
  2021-11-01 18:56   ` [PATCH v4 00/17] " Ævar Arnfjörð Bjarmason
                       ` (15 preceding siblings ...)
  2021-11-01 18:56     ` [PATCH v4 16/17] receive-pack: convert push-to-checkout hook to hook.h Ævar Arnfjörð Bjarmason
@ 2021-11-01 18:56     ` Ævar Arnfjörð Bjarmason
  2021-11-23 11:45     ` [PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
  17 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-01 18:56 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Phillip Wood, René Scharfe, Emily Shaffer,
	Bagas Sanjaya, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

The new hook.h library has replaced all run-command.h hook-related
functionality. So let's delete this dead code.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 run-command.c | 32 --------------------------------
 run-command.h | 16 ----------------
 2 files changed, 48 deletions(-)

diff --git a/run-command.c b/run-command.c
index d92e670c8ed..8a21ff525f3 100644
--- a/run-command.c
+++ b/run-command.c
@@ -1323,38 +1323,6 @@ int async_with_fork(void)
 #endif
 }
 
-static int run_hook_ve(const char *const *env, const char *name, va_list args)
-{
-	struct child_process hook = CHILD_PROCESS_INIT;
-	const char *p;
-
-	p = find_hook(name);
-	if (!p)
-		return 0;
-
-	strvec_push(&hook.args, p);
-	while ((p = va_arg(args, const char *)))
-		strvec_push(&hook.args, p);
-	hook.env = env;
-	hook.no_stdin = 1;
-	hook.stdout_to_stderr = 1;
-	hook.trace2_hook_name = name;
-
-	return run_command(&hook);
-}
-
-int run_hook_le(const char *const *env, const char *name, ...)
-{
-	va_list args;
-	int ret;
-
-	va_start(args, name);
-	ret = run_hook_ve(env, name, args);
-	va_end(args);
-
-	return ret;
-}
-
 struct io_pump {
 	/* initialized by caller */
 	int fd;
diff --git a/run-command.h b/run-command.h
index 3fa7454cf8a..59e1fbff64c 100644
--- a/run-command.h
+++ b/run-command.h
@@ -224,22 +224,6 @@ int finish_command_in_signal(struct child_process *);
  */
 int run_command(struct child_process *);
 
-/**
- * Run a hook.
- * The first argument is a pathname to an index file, or NULL
- * if the hook uses the default index file or no index is needed.
- * The second argument is the name of the hook.
- * The further arguments correspond to the hook arguments.
- * The last argument has to be NULL to terminate the arguments list.
- * If the hook does not exist or is not executable, the return
- * value will be zero.
- * If it is executable, the hook will be executed and the exit
- * status of the hook is returned.
- * On execution, .stdout_to_stderr and .no_stdin will be set.
- */
-LAST_ARG_MUST_BE_NULL
-int run_hook_le(const char *const *env, const char *name, ...);
-
 /*
  * Trigger an auto-gc
  */
-- 
2.33.1.1570.g069344fdd45


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion
  2021-11-01 18:56   ` [PATCH v4 00/17] " Ævar Arnfjörð Bjarmason
                       ` (16 preceding siblings ...)
  2021-11-01 18:56     ` [PATCH v4 17/17] run-command: remove old run_hook_{le,ve}() hook API Ævar Arnfjörð Bjarmason
@ 2021-11-23 11:45     ` Ævar Arnfjörð Bjarmason
  2021-11-23 11:46       ` [PATCH v5 01/17] hook: add 'run' subcommand Ævar Arnfjörð Bjarmason
                         ` (16 more replies)
  17 siblings, 17 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-23 11:45 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Bagas Sanjaya, Emily Shaffer, René Scharfe,
	Phillip Wood, Ævar Arnfjörð Bjarmason

A trivial update to the v4 of the series to migrate hook execution to
the new hook.c hook execution library. For a meaningful overall
summary see v4's CL[1].

The only update here is to stop using the "env" member of "struct
child_process" in favor of "env_array".

This is in preparation for a re-roll of another series[2] to remove
"argv" (and soon in a re-roll, "env") from that API. Without this
update the two would semantically conflict.

1. https://lore.kernel.org/git/cover-v4-00.17-00000000000-20211101T184938Z-avarab@gmail.com/
2. https://lore.kernel.org/git/cover-0.5-00000000000-20211122T153605Z-avarab@gmail.com/

Emily Shaffer (14):
  hook: add 'run' subcommand
  gc: use hook library for pre-auto-gc hook
  am: convert {pre,post}-applypatch to use hook.h
  rebase: convert pre-rebase to use hook.h
  am: convert applypatch-msg to use hook.h
  merge: convert post-merge to use hook.h
  hooks: convert non-worktree 'post-checkout' hook to hook library
  hooks: convert worktree 'post-checkout' hook to hook library
  send-email: use 'git hook run' for 'sendemail-validate'
  git-p4: use 'git hook' to run hooks
  commit: convert {pre-commit,prepare-commit-msg} hook to hook.h
  read-cache: convert post-index-change to use hook.h
  receive-pack: convert push-to-checkout hook to hook.h
  run-command: remove old run_hook_{le,ve}() hook API

Ævar Arnfjörð Bjarmason (3):
  hook API: add a run_hooks() wrapper
  hook API: add a run_hooks_l() wrapper
  git hook run: add an --ignore-missing flag

 .gitignore                 |   1 +
 Documentation/git-hook.txt |  45 +++++++++++++
 Documentation/githooks.txt |   4 ++
 Makefile                   |   1 +
 builtin.h                  |   1 +
 builtin/am.c               |   6 +-
 builtin/checkout.c         |   3 +-
 builtin/clone.c            |   3 +-
 builtin/gc.c               |   3 +-
 builtin/hook.c             |  84 +++++++++++++++++++++++
 builtin/merge.c            |   2 +-
 builtin/rebase.c           |   3 +-
 builtin/receive-pack.c     |   7 +-
 builtin/worktree.c         |  27 +++-----
 command-list.txt           |   1 +
 commit.c                   |  15 +++--
 git-p4.py                  |  70 ++-----------------
 git-send-email.perl        |  22 +++---
 git.c                      |   1 +
 hook.c                     | 131 ++++++++++++++++++++++++++++++++++++
 hook.h                     |  57 ++++++++++++++++
 read-cache.c               |   3 +-
 reset.c                    |   3 +-
 run-command.c              |  32 ---------
 run-command.h              |  17 -----
 t/t1800-hook.sh            | 134 +++++++++++++++++++++++++++++++++++++
 t/t9001-send-email.sh      |   4 +-
 27 files changed, 522 insertions(+), 158 deletions(-)
 create mode 100644 Documentation/git-hook.txt
 create mode 100644 builtin/hook.c
 create mode 100755 t/t1800-hook.sh

Range-diff against v4:
 1:  d97d6734961 !  1:  4ca52feebb8 hook: add 'run' subcommand
    @@ hook.c: int hook_exists(const char *name)
     +		return 0;
     +
     +	cp->no_stdin = 1;
    -+	cp->env = hook_cb->options->env.v;
    ++	strvec_pushv(&cp->env_array, hook_cb->options->env.v);
     +	cp->stdout_to_stderr = 1;
     +	cp->trace2_hook_name = hook_cb->hook_name;
     +
 2:  ca6464f7d5e =  2:  6275b97a306 hook API: add a run_hooks() wrapper
 3:  173860afca1 =  3:  b5b3051b2e5 gc: use hook library for pre-auto-gc hook
 4:  80a2171ddaf =  4:  c88eb5d4c25 am: convert {pre,post}-applypatch to use hook.h
 5:  74f459db287 =  5:  1d8f7b7e4c1 hook API: add a run_hooks_l() wrapper
 6:  1fd70c0e88a =  6:  d49a1444345 rebase: convert pre-rebase to use hook.h
 7:  ccba3ddf52e =  7:  191fdad0165 am: convert applypatch-msg to use hook.h
 8:  2c23e8645ec =  8:  119b92fbeae merge: convert post-merge to use hook.h
 9:  cb95c79093b =  9:  359ba416e84 hooks: convert non-worktree 'post-checkout' hook to hook library
10:  f330600fec8 ! 10:  b7599be95a7 hooks: convert worktree 'post-checkout' hook to hook library
    @@ builtin/worktree.c: static int add_worktree(const char *path, const char *refnam
     
      ## hook.c ##
     @@ hook.c: static int pick_next_hook(struct child_process *cp,
    - 	cp->env = hook_cb->options->env.v;
    + 	strvec_pushv(&cp->env_array, hook_cb->options->env.v);
      	cp->stdout_to_stderr = 1;
      	cp->trace2_hook_name = hook_cb->hook_name;
     +	cp->dir = hook_cb->options->dir;
11:  a0b6818c766 = 11:  f1c84d7f627 git hook run: add an --ignore-missing flag
12:  efa35971e9f = 12:  4e0f94d9102 send-email: use 'git hook run' for 'sendemail-validate'
13:  98e0e3330fb = 13:  e858f332a62 git-p4: use 'git hook' to run hooks
14:  79ea5a2a4f5 = 14:  9a5956cc028 commit: convert {pre-commit,prepare-commit-msg} hook to hook.h
15:  81612f94707 = 15:  6fd47c4c499 read-cache: convert post-index-change to use hook.h
16:  43ecd6697e0 = 16:  b201ea46f4b receive-pack: convert push-to-checkout hook to hook.h
17:  9ef574fa30c = 17:  281d17b04db run-command: remove old run_hook_{le,ve}() hook API
-- 
2.34.0.831.gd33babec0d1


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v5 01/17] hook: add 'run' subcommand
  2021-11-23 11:45     ` [PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
@ 2021-11-23 11:46       ` Ævar Arnfjörð Bjarmason
  2021-11-23 11:46       ` [PATCH v5 02/17] hook API: add a run_hooks() wrapper Ævar Arnfjörð Bjarmason
                         ` (15 subsequent siblings)
  16 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-23 11:46 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Bagas Sanjaya, Emily Shaffer, René Scharfe,
	Phillip Wood, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

In order to enable hooks to be run as an external process, by a
standalone Git command, or by tools which wrap Git, provide an external
means to run all configured hook commands for a given hook event.

Most of our hooks require more complex functionality than this, but
let's start with the bare minimum required to support our simplest
hooks.

In terms of implementation the usage_with_options() and "goto usage"
pattern here mirrors that of
builtin/{commit-graph,multi-pack-index}.c.

Some of the implementation here, such as a function being named
run_hooks_opt() when it's tasked with running one hook, to using the
run_processes_parallel_tr2() API to run with jobs=1 is somewhere
between a bit odd and and an overkill for the current features of this
"hook run" command and the hook.[ch] API.

This code will eventually be able to run multiple hooks declared in
config in parallel, by starting out with these names and APIs we
reduce the later churn of renaming functions, switching from the
run_command() to run_processes_parallel_tr2() API etc.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 .gitignore                 |   1 +
 Documentation/git-hook.txt |  37 +++++++++++
 Documentation/githooks.txt |   4 ++
 Makefile                   |   1 +
 builtin.h                  |   1 +
 builtin/hook.c             |  80 +++++++++++++++++++++++
 command-list.txt           |   1 +
 git.c                      |   1 +
 hook.c                     | 102 +++++++++++++++++++++++++++++
 hook.h                     |  35 ++++++++++
 t/t1800-hook.sh            | 129 +++++++++++++++++++++++++++++++++++++
 11 files changed, 392 insertions(+)
 create mode 100644 Documentation/git-hook.txt
 create mode 100644 builtin/hook.c
 create mode 100755 t/t1800-hook.sh

diff --git a/.gitignore b/.gitignore
index 054249b20a8..f817c509ec0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -77,6 +77,7 @@
 /git-grep
 /git-hash-object
 /git-help
+/git-hook
 /git-http-backend
 /git-http-fetch
 /git-http-push
diff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt
new file mode 100644
index 00000000000..e39b1b5d069
--- /dev/null
+++ b/Documentation/git-hook.txt
@@ -0,0 +1,37 @@
+git-hook(1)
+===========
+
+NAME
+----
+git-hook - Run git hooks
+
+SYNOPSIS
+--------
+[verse]
+'git hook' run <hook-name> [-- <hook-args>]
+
+DESCRIPTION
+-----------
+
+A command interface to running git hooks (see linkgit:githooks[5]),
+for use by other scripted git commands.
+
+SUBCOMMANDS
+-----------
+
+run::
+	Run the `<hook-name>` hook. See linkgit:githooks[5] for
+	supported hook names.
++
+
+Any positional arguments to the hook should be passed after a
+mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See
+linkgit:githooks[5] for arguments hooks might expect (if any).
+
+SEE ALSO
+--------
+linkgit:githooks[5]
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index b51959ff941..a16e62bc8c8 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -698,6 +698,10 @@ and "0" meaning they were not.
 Only one parameter should be set to "1" when the hook runs.  The hook
 running passing "1", "1" should not be possible.
 
+SEE ALSO
+--------
+linkgit:git-hook[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/Makefile b/Makefile
index 12be39ac497..6cd20534304 100644
--- a/Makefile
+++ b/Makefile
@@ -1107,6 +1107,7 @@ BUILTIN_OBJS += builtin/get-tar-commit-id.o
 BUILTIN_OBJS += builtin/grep.o
 BUILTIN_OBJS += builtin/hash-object.o
 BUILTIN_OBJS += builtin/help.o
+BUILTIN_OBJS += builtin/hook.o
 BUILTIN_OBJS += builtin/index-pack.o
 BUILTIN_OBJS += builtin/init-db.o
 BUILTIN_OBJS += builtin/interpret-trailers.o
diff --git a/builtin.h b/builtin.h
index 8a58743ed63..83379f3832c 100644
--- a/builtin.h
+++ b/builtin.h
@@ -164,6 +164,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);
 int cmd_grep(int argc, const char **argv, const char *prefix);
 int cmd_hash_object(int argc, const char **argv, const char *prefix);
 int cmd_help(int argc, const char **argv, const char *prefix);
+int cmd_hook(int argc, const char **argv, const char *prefix);
 int cmd_index_pack(int argc, const char **argv, const char *prefix);
 int cmd_init_db(int argc, const char **argv, const char *prefix);
 int cmd_interpret_trailers(int argc, const char **argv, const char *prefix);
diff --git a/builtin/hook.c b/builtin/hook.c
new file mode 100644
index 00000000000..9b67ff50cef
--- /dev/null
+++ b/builtin/hook.c
@@ -0,0 +1,80 @@
+#include "cache.h"
+#include "builtin.h"
+#include "config.h"
+#include "hook.h"
+#include "parse-options.h"
+#include "strbuf.h"
+#include "strvec.h"
+
+#define BUILTIN_HOOK_RUN_USAGE \
+	N_("git hook run <hook-name> [-- <hook-args>]")
+
+static const char * const builtin_hook_usage[] = {
+	BUILTIN_HOOK_RUN_USAGE,
+	NULL
+};
+
+static const char * const builtin_hook_run_usage[] = {
+	BUILTIN_HOOK_RUN_USAGE,
+	NULL
+};
+
+static int run(int argc, const char **argv, const char *prefix)
+{
+	int i;
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+	const char *hook_name;
+	struct option run_options[] = {
+		OPT_END(),
+	};
+	int ret;
+
+	argc = parse_options(argc, argv, prefix, run_options,
+			     builtin_hook_run_usage,
+			     PARSE_OPT_KEEP_DASHDASH);
+
+	if (!argc)
+		goto usage;
+
+	/*
+	 * Having a -- for "run" when providing <hook-args> is
+	 * mandatory.
+	 */
+	if (argc > 1 && strcmp(argv[1], "--") &&
+	    strcmp(argv[1], "--end-of-options"))
+		goto usage;
+
+	/* Add our arguments, start after -- */
+	for (i = 2 ; i < argc; i++)
+		strvec_push(&opt.args, argv[i]);
+
+	/* Need to take into account core.hooksPath */
+	git_config(git_default_config, NULL);
+
+	hook_name = argv[0];
+	opt.error_if_missing = 1;
+	ret = run_hooks_opt(hook_name, &opt);
+	if (ret < 0) /* error() return */
+		ret = 1;
+	return ret;
+usage:
+	usage_with_options(builtin_hook_run_usage, run_options);
+}
+
+int cmd_hook(int argc, const char **argv, const char *prefix)
+{
+	struct option builtin_hook_options[] = {
+		OPT_END(),
+	};
+
+	argc = parse_options(argc, argv, NULL, builtin_hook_options,
+			     builtin_hook_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+	if (!argc)
+		goto usage;
+
+	if (!strcmp(argv[0], "run"))
+		return run(argc, argv, prefix);
+
+usage:
+	usage_with_options(builtin_hook_usage, builtin_hook_options);
+}
diff --git a/command-list.txt b/command-list.txt
index eb9cee8dee9..0c9af14bdf3 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -103,6 +103,7 @@ git-grep                                mainporcelain           info
 git-gui                                 mainporcelain
 git-hash-object                         plumbingmanipulators
 git-help                                ancillaryinterrogators          complete
+git-hook                                purehelpers
 git-http-backend                        synchingrepositories
 git-http-fetch                          synchelpers
 git-http-push                           synchelpers
diff --git a/git.c b/git.c
index 5ff21be21f3..a5be02b04b8 100644
--- a/git.c
+++ b/git.c
@@ -538,6 +538,7 @@ static struct cmd_struct commands[] = {
 	{ "grep", cmd_grep, RUN_SETUP_GENTLY },
 	{ "hash-object", cmd_hash_object },
 	{ "help", cmd_help },
+	{ "hook", cmd_hook, RUN_SETUP },
 	{ "index-pack", cmd_index_pack, RUN_SETUP_GENTLY | NO_PARSEOPT },
 	{ "init", cmd_init_db },
 	{ "init-db", cmd_init_db },
diff --git a/hook.c b/hook.c
index 55e1145a4b7..a0917cf877c 100644
--- a/hook.c
+++ b/hook.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "hook.h"
 #include "run-command.h"
+#include "config.h"
 
 const char *find_hook(const char *name)
 {
@@ -40,3 +41,104 @@ int hook_exists(const char *name)
 {
 	return !!find_hook(name);
 }
+
+static int pick_next_hook(struct child_process *cp,
+			  struct strbuf *out,
+			  void *pp_cb,
+			  void **pp_task_cb)
+{
+	struct hook_cb_data *hook_cb = pp_cb;
+	const char *hook_path = hook_cb->hook_path;
+
+	if (!hook_path)
+		return 0;
+
+	cp->no_stdin = 1;
+	strvec_pushv(&cp->env_array, hook_cb->options->env.v);
+	cp->stdout_to_stderr = 1;
+	cp->trace2_hook_name = hook_cb->hook_name;
+
+	strvec_push(&cp->args, hook_path);
+	strvec_pushv(&cp->args, hook_cb->options->args.v);
+
+	/* Provide context for errors if necessary */
+	*pp_task_cb = (char *)hook_path;
+
+	/*
+	 * This pick_next_hook() will be called again, we're only
+	 * running one hook, so indicate that no more work will be
+	 * done.
+	 */
+	hook_cb->hook_path = NULL;
+
+	return 1;
+}
+
+static int notify_start_failure(struct strbuf *out,
+				void *pp_cb,
+				void *pp_task_cp)
+{
+	struct hook_cb_data *hook_cb = pp_cb;
+	const char *hook_path = pp_task_cp;
+
+	hook_cb->rc |= 1;
+
+	strbuf_addf(out, _("Couldn't start hook '%s'\n"),
+		    hook_path);
+
+	return 1;
+}
+
+static int notify_hook_finished(int result,
+				struct strbuf *out,
+				void *pp_cb,
+				void *pp_task_cb)
+{
+	struct hook_cb_data *hook_cb = pp_cb;
+
+	hook_cb->rc |= result;
+
+	return 0;
+}
+
+static void run_hooks_opt_clear(struct run_hooks_opt *options)
+{
+	strvec_clear(&options->env);
+	strvec_clear(&options->args);
+}
+
+int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)
+{
+	struct hook_cb_data cb_data = {
+		.rc = 0,
+		.hook_name = hook_name,
+		.options = options,
+	};
+	const char *const hook_path = find_hook(hook_name);
+	int jobs = 1;
+	int ret = 0;
+
+	if (!options)
+		BUG("a struct run_hooks_opt must be provided to run_hooks");
+
+	if (!hook_path && !options->error_if_missing)
+		goto cleanup;
+
+	if (!hook_path) {
+		ret = error("cannot find a hook named %s", hook_name);
+		goto cleanup;
+	}
+
+	cb_data.hook_path = hook_path;
+	run_processes_parallel_tr2(jobs,
+				   pick_next_hook,
+				   notify_start_failure,
+				   notify_hook_finished,
+				   &cb_data,
+				   "hook",
+				   hook_name);
+	ret = cb_data.rc;
+cleanup:
+	run_hooks_opt_clear(options);
+	return ret;
+}
diff --git a/hook.h b/hook.h
index 6aa36fc7ff9..782385cc235 100644
--- a/hook.h
+++ b/hook.h
@@ -1,5 +1,31 @@
 #ifndef HOOK_H
 #define HOOK_H
+#include "strvec.h"
+
+struct run_hooks_opt
+{
+	/* Environment vars to be set for each hook */
+	struct strvec env;
+
+	/* Args to be passed to each hook */
+	struct strvec args;
+
+	/* Emit an error if the hook is missing */
+	unsigned int error_if_missing:1;
+};
+
+#define RUN_HOOKS_OPT_INIT { \
+	.env = STRVEC_INIT, \
+	.args = STRVEC_INIT, \
+}
+
+struct hook_cb_data {
+	/* rc reflects the cumulative failure state */
+	int rc;
+	const char *hook_name;
+	const char *hook_path;
+	struct run_hooks_opt *options;
+};
 
 /*
  * Returns the path to the hook file, or NULL if the hook is missing
@@ -13,4 +39,13 @@ const char *find_hook(const char *name);
  */
 int hook_exists(const char *hookname);
 
+/**
+ * Takes a `hook_name`, resolves it to a path with find_hook(), and
+ * runs the hook for you with the options specified in "struct
+ * run_hooks opt". Will free memory associated with the "struct run_hooks_opt".
+ *
+ * Returns the status code of the run hook, or a negative value on
+ * error().
+ */
+int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);
 #endif
diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh
new file mode 100755
index 00000000000..3aea1b105f0
--- /dev/null
+++ b/t/t1800-hook.sh
@@ -0,0 +1,129 @@
+#!/bin/sh
+
+test_description='git-hook command'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+
+test_expect_success 'git hook usage' '
+	test_expect_code 129 git hook &&
+	test_expect_code 129 git hook run &&
+	test_expect_code 129 git hook run -h &&
+	test_expect_code 129 git hook run --unknown 2>err &&
+	grep "unknown option" err
+'
+
+test_expect_success 'git hook run: nonexistent hook' '
+	cat >stderr.expect <<-\EOF &&
+	error: cannot find a hook named test-hook
+	EOF
+	test_expect_code 1 git hook run test-hook 2>stderr.actual &&
+	test_cmp stderr.expect stderr.actual
+'
+
+test_expect_success 'git hook run: basic' '
+	write_script .git/hooks/test-hook <<-EOF &&
+	echo Test hook
+	EOF
+
+	cat >expect <<-\EOF &&
+	Test hook
+	EOF
+	git hook run test-hook 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git hook run: stdout and stderr both write to our stderr' '
+	write_script .git/hooks/test-hook <<-EOF &&
+	echo >&1 Will end up on stderr
+	echo >&2 Will end up on stderr
+	EOF
+
+	cat >stderr.expect <<-\EOF &&
+	Will end up on stderr
+	Will end up on stderr
+	EOF
+	git hook run test-hook >stdout.actual 2>stderr.actual &&
+	test_cmp stderr.expect stderr.actual &&
+	test_must_be_empty stdout.actual
+'
+
+test_expect_success 'git hook run: exit codes are passed along' '
+	write_script .git/hooks/test-hook <<-EOF &&
+	exit 1
+	EOF
+
+	test_expect_code 1 git hook run test-hook &&
+
+	write_script .git/hooks/test-hook <<-EOF &&
+	exit 2
+	EOF
+
+	test_expect_code 2 git hook run test-hook &&
+
+	write_script .git/hooks/test-hook <<-EOF &&
+	exit 128
+	EOF
+
+	test_expect_code 128 git hook run test-hook &&
+
+	write_script .git/hooks/test-hook <<-EOF &&
+	exit 129
+	EOF
+
+	test_expect_code 129 git hook run test-hook
+'
+
+test_expect_success 'git hook run arg u ments without -- is not allowed' '
+	test_expect_code 129 git hook run test-hook arg u ments
+'
+
+test_expect_success 'git hook run -- pass arguments' '
+	write_script .git/hooks/test-hook <<-\EOF &&
+	echo $1
+	echo $2
+	EOF
+
+	cat >expect <<-EOF &&
+	arg
+	u ments
+	EOF
+
+	git hook run test-hook -- arg "u ments" 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git hook run -- out-of-repo runs excluded' '
+	write_script .git/hooks/test-hook <<-EOF &&
+	echo Test hook
+	EOF
+
+	nongit test_must_fail git hook run test-hook
+'
+
+test_expect_success 'git -c core.hooksPath=<PATH> hook run' '
+	mkdir my-hooks &&
+	write_script my-hooks/test-hook <<-\EOF &&
+	echo Hook ran $1 >>actual
+	EOF
+
+	cat >expect <<-\EOF &&
+	Test hook
+	Hook ran one
+	Hook ran two
+	Hook ran three
+	Hook ran four
+	EOF
+
+	# Test various ways of specifying the path. See also
+	# t1350-config-hooks-path.sh
+	>actual &&
+	git hook run test-hook -- ignored 2>>actual &&
+	git -c core.hooksPath=my-hooks hook run test-hook -- one 2>>actual &&
+	git -c core.hooksPath=my-hooks/ hook run test-hook -- two 2>>actual &&
+	git -c core.hooksPath="$PWD/my-hooks" hook run test-hook -- three 2>>actual &&
+	git -c core.hooksPath="$PWD/my-hooks/" hook run test-hook -- four 2>>actual &&
+	test_cmp expect actual
+'
+
+test_done
-- 
2.34.0.831.gd33babec0d1


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v5 02/17] hook API: add a run_hooks() wrapper
  2021-11-23 11:45     ` [PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
  2021-11-23 11:46       ` [PATCH v5 01/17] hook: add 'run' subcommand Ævar Arnfjörð Bjarmason
@ 2021-11-23 11:46       ` Ævar Arnfjörð Bjarmason
  2021-11-23 11:46       ` [PATCH v5 03/17] gc: use hook library for pre-auto-gc hook Ævar Arnfjörð Bjarmason
                         ` (14 subsequent siblings)
  16 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-23 11:46 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Bagas Sanjaya, Emily Shaffer, René Scharfe,
	Phillip Wood, Ævar Arnfjörð Bjarmason

Add a run_hooks() wrapper, we'll use it in subsequent commits for the
simple cases of wanting to run a single hook under a given name,
without providing options such as "env" or "args".

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 hook.c | 7 +++++++
 hook.h | 6 ++++++
 2 files changed, 13 insertions(+)

diff --git a/hook.c b/hook.c
index a0917cf877c..d67a114e62d 100644
--- a/hook.c
+++ b/hook.c
@@ -142,3 +142,10 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)
 	run_hooks_opt_clear(options);
 	return ret;
 }
+
+int run_hooks(const char *hook_name)
+{
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+
+	return run_hooks_opt(hook_name, &opt);
+}
diff --git a/hook.h b/hook.h
index 782385cc235..9c358789958 100644
--- a/hook.h
+++ b/hook.h
@@ -48,4 +48,10 @@ int hook_exists(const char *hookname);
  * error().
  */
 int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);
+
+/**
+ * A wrapper for run_hooks_opt() which provides a dummy "struct
+ * run_hooks_opt" initialized with "RUN_HOOKS_OPT_INIT".
+ */
+int run_hooks(const char *hook_name);
 #endif
-- 
2.34.0.831.gd33babec0d1


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v5 03/17] gc: use hook library for pre-auto-gc hook
  2021-11-23 11:45     ` [PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
  2021-11-23 11:46       ` [PATCH v5 01/17] hook: add 'run' subcommand Ævar Arnfjörð Bjarmason
  2021-11-23 11:46       ` [PATCH v5 02/17] hook API: add a run_hooks() wrapper Ævar Arnfjörð Bjarmason
@ 2021-11-23 11:46       ` Ævar Arnfjörð Bjarmason
  2021-11-23 11:46       ` [PATCH v5 04/17] am: convert {pre,post}-applypatch to use hook.h Ævar Arnfjörð Bjarmason
                         ` (13 subsequent siblings)
  16 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-23 11:46 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Bagas Sanjaya, Emily Shaffer, René Scharfe,
	Phillip Wood, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the pre-auto-gc hook away from run-command.h to and over to the
new hook.h library. This uses the new run_hooks() wrapper.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/gc.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/builtin/gc.c b/builtin/gc.c
index bcef6a4c8da..4bbc58aae5b 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -32,6 +32,7 @@
 #include "remote.h"
 #include "object-store.h"
 #include "exec-cmd.h"
+#include "hook.h"
 
 #define FAILED_RUN "failed to run %s"
 
@@ -394,7 +395,7 @@ static int need_to_gc(void)
 	else
 		return 0;
 
-	if (run_hook_le(NULL, "pre-auto-gc", NULL))
+	if (run_hooks("pre-auto-gc"))
 		return 0;
 	return 1;
 }
-- 
2.34.0.831.gd33babec0d1


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v5 04/17] am: convert {pre,post}-applypatch to use hook.h
  2021-11-23 11:45     ` [PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                         ` (2 preceding siblings ...)
  2021-11-23 11:46       ` [PATCH v5 03/17] gc: use hook library for pre-auto-gc hook Ævar Arnfjörð Bjarmason
@ 2021-11-23 11:46       ` Ævar Arnfjörð Bjarmason
  2021-11-23 11:46       ` [PATCH v5 05/17] hook API: add a run_hooks_l() wrapper Ævar Arnfjörð Bjarmason
                         ` (12 subsequent siblings)
  16 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-23 11:46 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Bagas Sanjaya, Emily Shaffer, René Scharfe,
	Phillip Wood, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Teach pre-applypatch and post-applypatch to use the hook.h library
instead of the run-command.h library.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/am.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index 8677ea2348a..4b334cb7b12 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1609,7 +1609,7 @@ static void do_commit(const struct am_state *state)
 	const char *reflog_msg, *author, *committer = NULL;
 	struct strbuf sb = STRBUF_INIT;
 
-	if (run_hook_le(NULL, "pre-applypatch", NULL))
+	if (run_hooks("pre-applypatch"))
 		exit(1);
 
 	if (write_cache_as_tree(&tree, 0, NULL))
@@ -1661,7 +1661,7 @@ static void do_commit(const struct am_state *state)
 		fclose(fp);
 	}
 
-	run_hook_le(NULL, "post-applypatch", NULL);
+	run_hooks("post-applypatch");
 
 	strbuf_release(&sb);
 }
-- 
2.34.0.831.gd33babec0d1


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v5 05/17] hook API: add a run_hooks_l() wrapper
  2021-11-23 11:45     ` [PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                         ` (3 preceding siblings ...)
  2021-11-23 11:46       ` [PATCH v5 04/17] am: convert {pre,post}-applypatch to use hook.h Ævar Arnfjörð Bjarmason
@ 2021-11-23 11:46       ` Ævar Arnfjörð Bjarmason
  2021-11-23 11:46       ` [PATCH v5 06/17] rebase: convert pre-rebase to use hook.h Ævar Arnfjörð Bjarmason
                         ` (11 subsequent siblings)
  16 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-23 11:46 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Bagas Sanjaya, Emily Shaffer, René Scharfe,
	Phillip Wood, Ævar Arnfjörð Bjarmason

Add a run_hooks_l() wrapper, we'll use it in subsequent commits for
the simple cases of wanting to run a single hook under a given name
along with a list of arguments.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 hook.c | 14 ++++++++++++++
 hook.h | 10 ++++++++++
 2 files changed, 24 insertions(+)

diff --git a/hook.c b/hook.c
index d67a114e62d..1ad123422b2 100644
--- a/hook.c
+++ b/hook.c
@@ -149,3 +149,17 @@ int run_hooks(const char *hook_name)
 
 	return run_hooks_opt(hook_name, &opt);
 }
+
+int run_hooks_l(const char *hook_name, ...)
+{
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+	va_list ap;
+	const char *arg;
+
+	va_start(ap, hook_name);
+	while ((arg = va_arg(ap, const char *)))
+		strvec_push(&opt.args, arg);
+	va_end(ap);
+
+	return run_hooks_opt(hook_name, &opt);
+}
diff --git a/hook.h b/hook.h
index 9c358789958..54528395953 100644
--- a/hook.h
+++ b/hook.h
@@ -54,4 +54,14 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options);
  * run_hooks_opt" initialized with "RUN_HOOKS_OPT_INIT".
  */
 int run_hooks(const char *hook_name);
+
+/**
+ * Like run_hooks(), a wrapper for run_hooks_opt().
+ *
+ * In addition to the wrapping behavior provided by run_hooks(), this
+ * wrapper takes a list of strings terminated by a NULL
+ * argument. These things will be used as positional arguments to the
+ * hook. This function behaves like the old run_hook_le() API.
+ */
+int run_hooks_l(const char *hook_name, ...);
 #endif
-- 
2.34.0.831.gd33babec0d1


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v5 06/17] rebase: convert pre-rebase to use hook.h
  2021-11-23 11:45     ` [PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                         ` (4 preceding siblings ...)
  2021-11-23 11:46       ` [PATCH v5 05/17] hook API: add a run_hooks_l() wrapper Ævar Arnfjörð Bjarmason
@ 2021-11-23 11:46       ` Ævar Arnfjörð Bjarmason
  2021-11-23 11:46       ` [PATCH v5 07/17] am: convert applypatch-msg " Ævar Arnfjörð Bjarmason
                         ` (10 subsequent siblings)
  16 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-23 11:46 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Bagas Sanjaya, Emily Shaffer, René Scharfe,
	Phillip Wood, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the pre-rebase hook away from run-command.h to and over to the
new hook.h library.

Since this hook needs arguments introduce a run_hooksl() wrapper, like
run_hooks(), but it takes varargs.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/rebase.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/builtin/rebase.c b/builtin/rebase.c
index 34b4744e5f3..ac4120013a0 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -28,6 +28,7 @@
 #include "sequencer.h"
 #include "rebase-interactive.h"
 #include "reset.h"
+#include "hook.h"
 
 #define DEFAULT_REFLOG_ACTION "rebase"
 
@@ -1712,7 +1713,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
 
 	/* If a hook exists, give it a chance to interrupt*/
 	if (!ok_to_skip_pre_rebase &&
-	    run_hook_le(NULL, "pre-rebase", options.upstream_arg,
+	    run_hooks_l("pre-rebase", options.upstream_arg,
 			argc ? argv[0] : NULL, NULL))
 		die(_("The pre-rebase hook refused to rebase."));
 
-- 
2.34.0.831.gd33babec0d1


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v5 07/17] am: convert applypatch-msg to use hook.h
  2021-11-23 11:45     ` [PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                         ` (5 preceding siblings ...)
  2021-11-23 11:46       ` [PATCH v5 06/17] rebase: convert pre-rebase to use hook.h Ævar Arnfjörð Bjarmason
@ 2021-11-23 11:46       ` Ævar Arnfjörð Bjarmason
  2021-11-23 11:46       ` [PATCH v5 08/17] merge: convert post-merge " Ævar Arnfjörð Bjarmason
                         ` (9 subsequent siblings)
  16 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-23 11:46 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Bagas Sanjaya, Emily Shaffer, René Scharfe,
	Phillip Wood, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Teach applypatch-msg to use the hook.h library instead of the
run-command.h library.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/am.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/am.c b/builtin/am.c
index 4b334cb7b12..ae0c484dcba 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -448,7 +448,7 @@ static int run_applypatch_msg_hook(struct am_state *state)
 	int ret;
 
 	assert(state->msg);
-	ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL);
+	ret = run_hooks_l("applypatch-msg", am_path(state, "final-commit"), NULL);
 
 	if (!ret) {
 		FREE_AND_NULL(state->msg);
-- 
2.34.0.831.gd33babec0d1


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v5 08/17] merge: convert post-merge to use hook.h
  2021-11-23 11:45     ` [PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                         ` (6 preceding siblings ...)
  2021-11-23 11:46       ` [PATCH v5 07/17] am: convert applypatch-msg " Ævar Arnfjörð Bjarmason
@ 2021-11-23 11:46       ` Ævar Arnfjörð Bjarmason
  2021-11-23 11:46       ` [PATCH v5 09/17] hooks: convert non-worktree 'post-checkout' hook to hook library Ævar Arnfjörð Bjarmason
                         ` (8 subsequent siblings)
  16 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-23 11:46 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Bagas Sanjaya, Emily Shaffer, René Scharfe,
	Phillip Wood, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Teach post-merge to use the hook.h library instead of the
run-command.h library to run hooks.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/merge.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/merge.c b/builtin/merge.c
index ea3112e0c0b..e6facd1c95d 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -488,7 +488,7 @@ static void finish(struct commit *head_commit,
 	}
 
 	/* Run a post-merge hook */
-	run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL);
+	run_hooks_l("post-merge", squash ? "1" : "0", NULL);
 
 	apply_autostash(git_path_merge_autostash(the_repository));
 	strbuf_release(&reflog_message);
-- 
2.34.0.831.gd33babec0d1


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v5 09/17] hooks: convert non-worktree 'post-checkout' hook to hook library
  2021-11-23 11:45     ` [PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                         ` (7 preceding siblings ...)
  2021-11-23 11:46       ` [PATCH v5 08/17] merge: convert post-merge " Ævar Arnfjörð Bjarmason
@ 2021-11-23 11:46       ` Ævar Arnfjörð Bjarmason
  2021-11-23 11:46       ` [PATCH v5 10/17] hooks: convert worktree " Ævar Arnfjörð Bjarmason
                         ` (7 subsequent siblings)
  16 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-23 11:46 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Bagas Sanjaya, Emily Shaffer, René Scharfe,
	Phillip Wood, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the running of the 'post-checkout' hook away from run-command.h
to the new hook.h library, except in the case of
builtin/worktree.c. That special-case will be handled in a subsequent
commit.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/checkout.c | 3 ++-
 builtin/clone.c    | 3 ++-
 reset.c            | 3 ++-
 3 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/builtin/checkout.c b/builtin/checkout.c
index cbf73b8c9f6..4af17d17217 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -9,6 +9,7 @@
 #include "config.h"
 #include "diff.h"
 #include "dir.h"
+#include "hook.h"
 #include "ll-merge.h"
 #include "lockfile.h"
 #include "merge-recursive.h"
@@ -106,7 +107,7 @@ struct branch_info {
 static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,
 			      int changed)
 {
-	return run_hook_le(NULL, "post-checkout",
+	return run_hooks_l("post-checkout",
 			   oid_to_hex(old_commit ? &old_commit->object.oid : null_oid()),
 			   oid_to_hex(new_commit ? &new_commit->object.oid : null_oid()),
 			   changed ? "1" : "0", NULL);
diff --git a/builtin/clone.c b/builtin/clone.c
index fb377b27657..ee27b9f8114 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -32,6 +32,7 @@
 #include "connected.h"
 #include "packfile.h"
 #include "list-objects-filter-options.h"
+#include "hook.h"
 
 /*
  * Overall FIXMEs:
@@ -705,7 +706,7 @@ static int checkout(int submodule_progress)
 	if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
 		die(_("unable to write new index file"));
 
-	err |= run_hook_le(NULL, "post-checkout", oid_to_hex(null_oid()),
+	err |= run_hooks_l("post-checkout", oid_to_hex(null_oid()),
 			   oid_to_hex(&oid), "1", NULL);
 
 	if (!err && (option_recurse_submodules.nr > 0)) {
diff --git a/reset.c b/reset.c
index f214df3d96c..0881e636915 100644
--- a/reset.c
+++ b/reset.c
@@ -7,6 +7,7 @@
 #include "tree-walk.h"
 #include "tree.h"
 #include "unpack-trees.h"
+#include "hook.h"
 
 int reset_head(struct repository *r, struct object_id *oid, const char *action,
 	       const char *switch_to_branch, unsigned flags,
@@ -127,7 +128,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,
 					    reflog_head);
 	}
 	if (run_hook)
-		run_hook_le(NULL, "post-checkout",
+		run_hooks_l("post-checkout",
 			    oid_to_hex(orig ? orig : null_oid()),
 			    oid_to_hex(oid), "1", NULL);
 
-- 
2.34.0.831.gd33babec0d1


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v5 10/17] hooks: convert worktree 'post-checkout' hook to hook library
  2021-11-23 11:45     ` [PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                         ` (8 preceding siblings ...)
  2021-11-23 11:46       ` [PATCH v5 09/17] hooks: convert non-worktree 'post-checkout' hook to hook library Ævar Arnfjörð Bjarmason
@ 2021-11-23 11:46       ` Ævar Arnfjörð Bjarmason
  2021-11-23 11:46       ` [PATCH v5 11/17] git hook run: add an --ignore-missing flag Ævar Arnfjörð Bjarmason
                         ` (6 subsequent siblings)
  16 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-23 11:46 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Bagas Sanjaya, Emily Shaffer, René Scharfe,
	Phillip Wood, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the running of the 'post-checkout' hook away from run-command.h
to the new hook.h library in builtin/worktree.c. For this special case
we need a change to the hook API to teach it to run the hook from a
given directory.

We cannot skip the "absolute_path" flag and just check if "dir" is
specified as we'd then fail to find our hook in the new dir we'd
chdir() to. We currently don't have a use-case for running a hook not
in our "base" repository at a given absolute path, so let's have "dir"
imply absolute_path(find_hook(hook_name)).

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/worktree.c | 27 +++++++++++----------------
 hook.c             |  8 ++++++++
 hook.h             |  6 ++++++
 3 files changed, 25 insertions(+), 16 deletions(-)

diff --git a/builtin/worktree.c b/builtin/worktree.c
index d22ece93e1a..4d9df5ecc4c 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -382,22 +382,17 @@ static int add_worktree(const char *path, const char *refname,
 	 * is_junk is cleared, but do return appropriate code when hook fails.
 	 */
 	if (!ret && opts->checkout) {
-		const char *hook = find_hook("post-checkout");
-		if (hook) {
-			const char *env[] = { "GIT_DIR", "GIT_WORK_TREE", NULL };
-			cp.git_cmd = 0;
-			cp.no_stdin = 1;
-			cp.stdout_to_stderr = 1;
-			cp.dir = path;
-			cp.env = env;
-			cp.argv = NULL;
-			cp.trace2_hook_name = "post-checkout";
-			strvec_pushl(&cp.args, absolute_path(hook),
-				     oid_to_hex(null_oid()),
-				     oid_to_hex(&commit->object.oid),
-				     "1", NULL);
-			ret = run_command(&cp);
-		}
+		struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+
+		strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL);
+		strvec_pushl(&opt.args,
+			     oid_to_hex(null_oid()),
+			     oid_to_hex(&commit->object.oid),
+			     "1",
+			     NULL);
+		opt.dir = path;
+
+		ret = run_hooks_opt("post-checkout", &opt);
 	}
 
 	strvec_clear(&child_env);
diff --git a/hook.c b/hook.c
index 1ad123422b2..69a215b2c3c 100644
--- a/hook.c
+++ b/hook.c
@@ -57,6 +57,7 @@ static int pick_next_hook(struct child_process *cp,
 	strvec_pushv(&cp->env_array, hook_cb->options->env.v);
 	cp->stdout_to_stderr = 1;
 	cp->trace2_hook_name = hook_cb->hook_name;
+	cp->dir = hook_cb->options->dir;
 
 	strvec_push(&cp->args, hook_path);
 	strvec_pushv(&cp->args, hook_cb->options->args.v);
@@ -109,6 +110,7 @@ static void run_hooks_opt_clear(struct run_hooks_opt *options)
 
 int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)
 {
+	struct strbuf abs_path = STRBUF_INIT;
 	struct hook_cb_data cb_data = {
 		.rc = 0,
 		.hook_name = hook_name,
@@ -130,6 +132,11 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)
 	}
 
 	cb_data.hook_path = hook_path;
+	if (options->dir) {
+		strbuf_add_absolute_path(&abs_path, hook_path);
+		cb_data.hook_path = abs_path.buf;
+	}
+
 	run_processes_parallel_tr2(jobs,
 				   pick_next_hook,
 				   notify_start_failure,
@@ -139,6 +146,7 @@ int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options)
 				   hook_name);
 	ret = cb_data.rc;
 cleanup:
+	strbuf_release(&abs_path);
 	run_hooks_opt_clear(options);
 	return ret;
 }
diff --git a/hook.h b/hook.h
index 54528395953..18d90aedf14 100644
--- a/hook.h
+++ b/hook.h
@@ -12,6 +12,12 @@ struct run_hooks_opt
 
 	/* Emit an error if the hook is missing */
 	unsigned int error_if_missing:1;
+
+	/**
+	 * An optional initial working directory for the hook,
+	 * translates to "struct child_process"'s "dir" member.
+	 */
+	const char *dir;
 };
 
 #define RUN_HOOKS_OPT_INIT { \
-- 
2.34.0.831.gd33babec0d1


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v5 11/17] git hook run: add an --ignore-missing flag
  2021-11-23 11:45     ` [PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                         ` (9 preceding siblings ...)
  2021-11-23 11:46       ` [PATCH v5 10/17] hooks: convert worktree " Ævar Arnfjörð Bjarmason
@ 2021-11-23 11:46       ` Ævar Arnfjörð Bjarmason
  2021-11-23 11:46       ` [PATCH v5 12/17] send-email: use 'git hook run' for 'sendemail-validate' Ævar Arnfjörð Bjarmason
                         ` (5 subsequent siblings)
  16 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-23 11:46 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Bagas Sanjaya, Emily Shaffer, René Scharfe,
	Phillip Wood, Ævar Arnfjörð Bjarmason

For certain one-shot hooks we'd like to optimistically run them, and
not complain if they don't exist.

This was already supported by the underlying hook.c library, but had
not been exposed via "git hook run". The command version of this will
be used by send-email in a subsequent commit.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 Documentation/git-hook.txt | 10 +++++++++-
 builtin/hook.c             |  8 ++++++--
 t/t1800-hook.sh            |  5 +++++
 3 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt
index e39b1b5d069..77c3a8ad909 100644
--- a/Documentation/git-hook.txt
+++ b/Documentation/git-hook.txt
@@ -8,7 +8,7 @@ git-hook - Run git hooks
 SYNOPSIS
 --------
 [verse]
-'git hook' run <hook-name> [-- <hook-args>]
+'git hook' run [--ignore-missing] <hook-name> [-- <hook-args>]
 
 DESCRIPTION
 -----------
@@ -28,6 +28,14 @@ Any positional arguments to the hook should be passed after a
 mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See
 linkgit:githooks[5] for arguments hooks might expect (if any).
 
+OPTIONS
+-------
+
+--ignore-missing::
+	Ignore any missing hook by quietly returning zero. Used for
+	tools that want to do a blind one-shot run of a hook that may
+	or may not be present.
+
 SEE ALSO
 --------
 linkgit:githooks[5]
diff --git a/builtin/hook.c b/builtin/hook.c
index 9b67ff50cef..54e5c6ec933 100644
--- a/builtin/hook.c
+++ b/builtin/hook.c
@@ -7,7 +7,7 @@
 #include "strvec.h"
 
 #define BUILTIN_HOOK_RUN_USAGE \
-	N_("git hook run <hook-name> [-- <hook-args>]")
+	N_("git hook run [--ignore-missing] <hook-name> [-- <hook-args>]")
 
 static const char * const builtin_hook_usage[] = {
 	BUILTIN_HOOK_RUN_USAGE,
@@ -23,8 +23,11 @@ static int run(int argc, const char **argv, const char *prefix)
 {
 	int i;
 	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+	int ignore_missing = 0;
 	const char *hook_name;
 	struct option run_options[] = {
+		OPT_BOOL(0, "ignore-missing", &ignore_missing,
+			 N_("silently ignore missing requested <hook-name>")),
 		OPT_END(),
 	};
 	int ret;
@@ -52,7 +55,8 @@ static int run(int argc, const char **argv, const char *prefix)
 	git_config(git_default_config, NULL);
 
 	hook_name = argv[0];
-	opt.error_if_missing = 1;
+	if (!ignore_missing)
+		opt.error_if_missing = 1;
 	ret = run_hooks_opt(hook_name, &opt);
 	if (ret < 0) /* error() return */
 		ret = 1;
diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh
index 3aea1b105f0..29718aa9913 100755
--- a/t/t1800-hook.sh
+++ b/t/t1800-hook.sh
@@ -21,6 +21,11 @@ test_expect_success 'git hook run: nonexistent hook' '
 	test_cmp stderr.expect stderr.actual
 '
 
+test_expect_success 'git hook run: nonexistent hook with --ignore-missing' '
+	git hook run --ignore-missing does-not-exist 2>stderr.actual &&
+	test_must_be_empty stderr.actual
+'
+
 test_expect_success 'git hook run: basic' '
 	write_script .git/hooks/test-hook <<-EOF &&
 	echo Test hook
-- 
2.34.0.831.gd33babec0d1


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v5 12/17] send-email: use 'git hook run' for 'sendemail-validate'
  2021-11-23 11:45     ` [PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                         ` (10 preceding siblings ...)
  2021-11-23 11:46       ` [PATCH v5 11/17] git hook run: add an --ignore-missing flag Ævar Arnfjörð Bjarmason
@ 2021-11-23 11:46       ` Ævar Arnfjörð Bjarmason
  2021-11-23 11:46       ` [PATCH v5 13/17] git-p4: use 'git hook' to run hooks Ævar Arnfjörð Bjarmason
                         ` (4 subsequent siblings)
  16 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-23 11:46 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Bagas Sanjaya, Emily Shaffer, René Scharfe,
	Phillip Wood, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Change the "sendmail-validate" hook to be run via the "git hook run"
wrapper instead of via a direct invocation.

This is the smallest possibly change to get "send-email" using "git
hook run". We still check the hook itself with "-x", and set a
"GIT_DIR" variable, both of which are asserted by our tests. We'll
need to get rid of this special behavior if we start running N hooks,
but for now let's be as close to bug-for-bug compatible as possible.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 git-send-email.perl   | 22 ++++++++++++++--------
 t/t9001-send-email.sh |  4 ++--
 2 files changed, 16 insertions(+), 10 deletions(-)

diff --git a/git-send-email.perl b/git-send-email.perl
index 5262d88ee32..4c20c0bbb79 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -195,13 +195,13 @@ sub format_2822_time {
 my $editor;
 
 sub system_or_msg {
-	my ($args, $msg) = @_;
+	my ($args, $msg, $cmd_name) = @_;
 	system(@$args);
 	my $signalled = $? & 127;
 	my $exit_code = $? >> 8;
 	return unless $signalled or $exit_code;
 
-	my @sprintf_args = ($args->[0], $exit_code);
+	my @sprintf_args = ($cmd_name ? $cmd_name : $args->[0], $exit_code);
 	if (defined $msg) {
 		# Quiet the 'redundant' warning category, except we
 		# need to support down to Perl 5.8, so we can't do a
@@ -2039,10 +2039,10 @@ sub validate_patch {
 	my ($fn, $xfer_encoding) = @_;
 
 	if ($repo) {
+		my $hook_name = 'sendemail-validate';
 		my $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks');
 		require File::Spec;
-		my $validate_hook = File::Spec->catfile($hooks_path,
-					    'sendemail-validate');
+		my $validate_hook = File::Spec->catfile($hooks_path, $hook_name);
 		my $hook_error;
 		if (-x $validate_hook) {
 			require Cwd;
@@ -2052,13 +2052,19 @@ sub validate_patch {
 			chdir($repo->wc_path() or $repo->repo_path())
 				or die("chdir: $!");
 			local $ENV{"GIT_DIR"} = $repo->repo_path();
-			$hook_error = system_or_msg([$validate_hook, $target]);
+			my @cmd = ("git", "hook", "run", "--ignore-missing",
+				    $hook_name, "--");
+			my @cmd_msg = (@cmd, "<patch>");
+			my @cmd_run = (@cmd, $target);
+			$hook_error = system_or_msg(\@cmd_run, undef, "@cmd_msg");
 			chdir($cwd_save) or die("chdir: $!");
 		}
 		if ($hook_error) {
-			die sprintf(__("fatal: %s: rejected by sendemail-validate hook\n" .
-				       "%s\n" .
-				       "warning: no patches were sent\n"), $fn, $hook_error);
+			$hook_error = sprintf(__("fatal: %s: rejected by %s hook\n" .
+						 $hook_error . "\n" .
+						 "warning: no patches were sent\n"),
+					      $fn, $hook_name);
+			die $hook_error;
 		}
 	}
 
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index aa0c20499ba..84d0f40d76a 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -539,7 +539,7 @@ test_expect_success $PREREQ "--validate respects relative core.hooksPath path" '
 	test_path_is_file my-hooks.ran &&
 	cat >expect <<-EOF &&
 	fatal: longline.patch: rejected by sendemail-validate hook
-	fatal: command '"'"'my-hooks/sendemail-validate'"'"' died with exit code 1
+	fatal: command '"'"'git hook run --ignore-missing sendemail-validate -- <patch>'"'"' died with exit code 1
 	warning: no patches were sent
 	EOF
 	test_cmp expect actual
@@ -558,7 +558,7 @@ test_expect_success $PREREQ "--validate respects absolute core.hooksPath path" '
 	test_path_is_file my-hooks.ran &&
 	cat >expect <<-EOF &&
 	fatal: longline.patch: rejected by sendemail-validate hook
-	fatal: command '"'"'$hooks_path/sendemail-validate'"'"' died with exit code 1
+	fatal: command '"'"'git hook run --ignore-missing sendemail-validate -- <patch>'"'"' died with exit code 1
 	warning: no patches were sent
 	EOF
 	test_cmp expect actual
-- 
2.34.0.831.gd33babec0d1


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v5 13/17] git-p4: use 'git hook' to run hooks
  2021-11-23 11:45     ` [PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                         ` (11 preceding siblings ...)
  2021-11-23 11:46       ` [PATCH v5 12/17] send-email: use 'git hook run' for 'sendemail-validate' Ævar Arnfjörð Bjarmason
@ 2021-11-23 11:46       ` Ævar Arnfjörð Bjarmason
  2021-11-23 11:46       ` [PATCH v5 14/17] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h Ævar Arnfjörð Bjarmason
                         ` (3 subsequent siblings)
  16 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-23 11:46 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Bagas Sanjaya, Emily Shaffer, René Scharfe,
	Phillip Wood, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Instead of duplicating the behavior of run-command.h:run_hook_le() in
Python, we can directly call 'git hook run'. We emulate the existence
check with the --ignore-missing flag.

We're dropping the "verbose" handling added in 9f59ca4d6af (git-p4:
create new function run_git_hook, 2020-02-11), those who want
diagnostic output about how hooks are run are now able to get that via
e.g. the trace2 facility and GIT_TRACE=1.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 git-p4.py | 70 +++++--------------------------------------------------
 1 file changed, 6 insertions(+), 64 deletions(-)

diff --git a/git-p4.py b/git-p4.py
index 2b4500226aa..3b54168eb4a 100755
--- a/git-p4.py
+++ b/git-p4.py
@@ -208,70 +208,12 @@ def decode_path(path):
 
 def run_git_hook(cmd, param=[]):
     """Execute a hook if the hook exists."""
-    if verbose:
-        sys.stderr.write("Looking for hook: %s\n" % cmd)
-        sys.stderr.flush()
-
-    hooks_path = gitConfig("core.hooksPath")
-    if len(hooks_path) <= 0:
-        hooks_path = os.path.join(os.environ["GIT_DIR"], "hooks")
-
-    if not isinstance(param, list):
-        param=[param]
-
-    # resolve hook file name, OS depdenent
-    hook_file = os.path.join(hooks_path, cmd)
-    if platform.system() == 'Windows':
-        if not os.path.isfile(hook_file):
-            # look for the file with an extension
-            files = glob.glob(hook_file + ".*")
-            if not files:
-                return True
-            files.sort()
-            hook_file = files.pop()
-            while hook_file.upper().endswith(".SAMPLE"):
-                # The file is a sample hook. We don't want it
-                if len(files) > 0:
-                    hook_file = files.pop()
-                else:
-                    return True
-
-    if not os.path.isfile(hook_file) or not os.access(hook_file, os.X_OK):
-        return True
-
-    return run_hook_command(hook_file, param) == 0
-
-def run_hook_command(cmd, param):
-    """Executes a git hook command
-       cmd = the command line file to be executed. This can be
-       a file that is run by OS association.
-
-       param = a list of parameters to pass to the cmd command
-
-       On windows, the extension is checked to see if it should
-       be run with the Git for Windows Bash shell.  If there
-       is no file extension, the file is deemed a bash shell
-       and will be handed off to sh.exe. Otherwise, Windows
-       will be called with the shell to handle the file assocation.
-
-       For non Windows operating systems, the file is called
-       as an executable.
-    """
-    cli = [cmd] + param
-    use_shell = False
-    if platform.system() == 'Windows':
-        (root,ext) = os.path.splitext(cmd)
-        if ext == "":
-            exe_path = os.environ.get("EXEPATH")
-            if exe_path is None:
-                exe_path = ""
-            else:
-                exe_path = os.path.join(exe_path, "bin")
-            cli = [os.path.join(exe_path, "SH.EXE")] + cli
-        else:
-            use_shell = True
-    return subprocess.call(cli, shell=use_shell)
-
+    args = ['git', 'hook', 'run', '--ignore-missing', cmd]
+    if param:
+        args.append("--")
+        for p in param:
+            args.append(p)
+    return subprocess.call(args) == 0
 
 def write_pipe(c, stdin):
     if verbose:
-- 
2.34.0.831.gd33babec0d1


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v5 14/17] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h
  2021-11-23 11:45     ` [PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                         ` (12 preceding siblings ...)
  2021-11-23 11:46       ` [PATCH v5 13/17] git-p4: use 'git hook' to run hooks Ævar Arnfjörð Bjarmason
@ 2021-11-23 11:46       ` Ævar Arnfjörð Bjarmason
  2021-11-23 11:46       ` [PATCH v5 15/17] read-cache: convert post-index-change to use hook.h Ævar Arnfjörð Bjarmason
                         ` (2 subsequent siblings)
  16 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-23 11:46 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Bagas Sanjaya, Emily Shaffer, René Scharfe,
	Phillip Wood, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move these hooks hook away from run-command.h to and over to the new
hook.h library.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 commit.c | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/commit.c b/commit.c
index 551de4903c1..581d7dc216c 100644
--- a/commit.c
+++ b/commit.c
@@ -21,6 +21,7 @@
 #include "commit-reach.h"
 #include "run-command.h"
 #include "shallow.h"
+#include "hook.h"
 
 static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
 
@@ -1700,22 +1701,22 @@ size_t ignore_non_trailer(const char *buf, size_t len)
 int run_commit_hook(int editor_is_used, const char *index_file,
 		    const char *name, ...)
 {
-	struct strvec hook_env = STRVEC_INIT;
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
 	va_list args;
-	int ret;
+	const char *arg;
 
-	strvec_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file);
+	strvec_pushf(&opt.env, "GIT_INDEX_FILE=%s", index_file);
 
 	/*
 	 * Let the hook know that no editor will be launched.
 	 */
 	if (!editor_is_used)
-		strvec_push(&hook_env, "GIT_EDITOR=:");
+		strvec_push(&opt.env, "GIT_EDITOR=:");
 
 	va_start(args, name);
-	ret = run_hook_ve(hook_env.v, name, args);
+	while ((arg = va_arg(args, const char *)))
+		strvec_push(&opt.args, arg);
 	va_end(args);
-	strvec_clear(&hook_env);
 
-	return ret;
+	return run_hooks_opt(name, &opt);
 }
-- 
2.34.0.831.gd33babec0d1


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v5 15/17] read-cache: convert post-index-change to use hook.h
  2021-11-23 11:45     ` [PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                         ` (13 preceding siblings ...)
  2021-11-23 11:46       ` [PATCH v5 14/17] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h Ævar Arnfjörð Bjarmason
@ 2021-11-23 11:46       ` Ævar Arnfjörð Bjarmason
  2021-11-23 11:46       ` [PATCH v5 16/17] receive-pack: convert push-to-checkout hook to hook.h Ævar Arnfjörð Bjarmason
  2021-11-23 11:46       ` [PATCH v5 17/17] run-command: remove old run_hook_{le,ve}() hook API Ævar Arnfjörð Bjarmason
  16 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-23 11:46 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Bagas Sanjaya, Emily Shaffer, René Scharfe,
	Phillip Wood, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the post-index-change hook away from run-command.h to and over to
the new hook.h library.

This removes the last direct user of "run_hook_ve()" outside of
run-command.c ("run_hook_le()" still uses it). So we can make the
function static now. A subsequent commit will remove this code
entirely when "run_hook_le()" itself goes away.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 read-cache.c  | 3 ++-
 run-command.c | 2 +-
 run-command.h | 1 -
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/read-cache.c b/read-cache.c
index f3986596623..4352661549f 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -28,6 +28,7 @@
 #include "sparse-index.h"
 #include "csum-file.h"
 #include "promisor-remote.h"
+#include "hook.h"
 
 /* Mask for the name length in ce_flags in the on-disk index */
 
@@ -3129,7 +3130,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
 	else
 		ret = close_lock_file_gently(lock);
 
-	run_hook_le(NULL, "post-index-change",
+	run_hooks_l("post-index-change",
 			istate->updated_workdir ? "1" : "0",
 			istate->updated_skipworktree ? "1" : "0", NULL);
 	istate->updated_workdir = 0;
diff --git a/run-command.c b/run-command.c
index f40df01c772..d7d64701ede 100644
--- a/run-command.c
+++ b/run-command.c
@@ -1323,7 +1323,7 @@ int async_with_fork(void)
 #endif
 }
 
-int run_hook_ve(const char *const *env, const char *name, va_list args)
+static int run_hook_ve(const char *const *env, const char *name, va_list args)
 {
 	struct child_process hook = CHILD_PROCESS_INIT;
 	const char *p;
diff --git a/run-command.h b/run-command.h
index 49878262584..3fa7454cf8a 100644
--- a/run-command.h
+++ b/run-command.h
@@ -239,7 +239,6 @@ int run_command(struct child_process *);
  */
 LAST_ARG_MUST_BE_NULL
 int run_hook_le(const char *const *env, const char *name, ...);
-int run_hook_ve(const char *const *env, const char *name, va_list args);
 
 /*
  * Trigger an auto-gc
-- 
2.34.0.831.gd33babec0d1


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v5 16/17] receive-pack: convert push-to-checkout hook to hook.h
  2021-11-23 11:45     ` [PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                         ` (14 preceding siblings ...)
  2021-11-23 11:46       ` [PATCH v5 15/17] read-cache: convert post-index-change to use hook.h Ævar Arnfjörð Bjarmason
@ 2021-11-23 11:46       ` Ævar Arnfjörð Bjarmason
  2021-11-23 11:46       ` [PATCH v5 17/17] run-command: remove old run_hook_{le,ve}() hook API Ævar Arnfjörð Bjarmason
  16 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-23 11:46 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Bagas Sanjaya, Emily Shaffer, René Scharfe,
	Phillip Wood, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

Move the push-to-checkout hook away from run-command.h to and over to
the new hook.h library.

This removes the last direct user of run_hook_le(), so we could remove
that function now, but let's leave that to a follow-up cleanup commit.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/receive-pack.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 49b846d9605..5d6d8dd9a26 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1439,9 +1439,12 @@ static const char *push_to_checkout(unsigned char *hash,
 				    struct strvec *env,
 				    const char *work_tree)
 {
+	struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+
 	strvec_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree));
-	if (run_hook_le(env->v, push_to_checkout_hook,
-			hash_to_hex(hash), NULL))
+	strvec_pushv(&opt.env, env->v);
+	strvec_push(&opt.args, hash_to_hex(hash));
+	if (run_hooks_opt(push_to_checkout_hook, &opt))
 		return "push-to-checkout hook declined";
 	else
 		return NULL;
-- 
2.34.0.831.gd33babec0d1


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v5 17/17] run-command: remove old run_hook_{le,ve}() hook API
  2021-11-23 11:45     ` [PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
                         ` (15 preceding siblings ...)
  2021-11-23 11:46       ` [PATCH v5 16/17] receive-pack: convert push-to-checkout hook to hook.h Ævar Arnfjörð Bjarmason
@ 2021-11-23 11:46       ` Ævar Arnfjörð Bjarmason
  16 siblings, 0 replies; 88+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-23 11:46 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Bagas Sanjaya, Emily Shaffer, René Scharfe,
	Phillip Wood, Ævar Arnfjörð Bjarmason

From: Emily Shaffer <emilyshaffer@google.com>

The new hook.h library has replaced all run-command.h hook-related
functionality. So let's delete this dead code.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 run-command.c | 32 --------------------------------
 run-command.h | 16 ----------------
 2 files changed, 48 deletions(-)

diff --git a/run-command.c b/run-command.c
index d7d64701ede..7ecff26c1dc 100644
--- a/run-command.c
+++ b/run-command.c
@@ -1323,38 +1323,6 @@ int async_with_fork(void)
 #endif
 }
 
-static int run_hook_ve(const char *const *env, const char *name, va_list args)
-{
-	struct child_process hook = CHILD_PROCESS_INIT;
-	const char *p;
-
-	p = find_hook(name);
-	if (!p)
-		return 0;
-
-	strvec_push(&hook.args, p);
-	while ((p = va_arg(args, const char *)))
-		strvec_push(&hook.args, p);
-	hook.env = env;
-	hook.no_stdin = 1;
-	hook.stdout_to_stderr = 1;
-	hook.trace2_hook_name = name;
-
-	return run_command(&hook);
-}
-
-int run_hook_le(const char *const *env, const char *name, ...)
-{
-	va_list args;
-	int ret;
-
-	va_start(args, name);
-	ret = run_hook_ve(env, name, args);
-	va_end(args);
-
-	return ret;
-}
-
 struct io_pump {
 	/* initialized by caller */
 	int fd;
diff --git a/run-command.h b/run-command.h
index 3fa7454cf8a..59e1fbff64c 100644
--- a/run-command.h
+++ b/run-command.h
@@ -224,22 +224,6 @@ int finish_command_in_signal(struct child_process *);
  */
 int run_command(struct child_process *);
 
-/**
- * Run a hook.
- * The first argument is a pathname to an index file, or NULL
- * if the hook uses the default index file or no index is needed.
- * The second argument is the name of the hook.
- * The further arguments correspond to the hook arguments.
- * The last argument has to be NULL to terminate the arguments list.
- * If the hook does not exist or is not executable, the return
- * value will be zero.
- * If it is executable, the hook will be executed and the exit
- * status of the hook is returned.
- * On execution, .stdout_to_stderr and .no_stdin will be set.
- */
-LAST_ARG_MUST_BE_NULL
-int run_hook_le(const char *const *env, const char *name, ...);
-
 /*
  * Trigger an auto-gc
  */
-- 
2.34.0.831.gd33babec0d1


^ permalink raw reply	[flat|nested] 88+ messages in thread

end of thread, other threads:[~2021-11-23 11:46 UTC | newest]

Thread overview: 88+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-10-15  9:43 [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
2021-10-15  9:43 ` [PATCH v2 01/13] hook: add 'run' subcommand Ævar Arnfjörð Bjarmason
2021-10-15 16:24   ` Emily Shaffer
2021-10-15 17:53     ` Eric Sunshine
2021-10-19 23:08     ` Ævar Arnfjörð Bjarmason
2021-10-15  9:43 ` [PATCH v2 02/13] gc: use hook library for pre-auto-gc hook Ævar Arnfjörð Bjarmason
2021-10-15 16:24   ` Emily Shaffer
2021-10-15  9:43 ` [PATCH v2 03/13] rebase: convert pre-rebase to use hook.h Ævar Arnfjörð Bjarmason
2021-10-15 16:25   ` Emily Shaffer
2021-10-15  9:43 ` [PATCH v2 04/13] am: convert applypatch " Ævar Arnfjörð Bjarmason
2021-10-15 16:25   ` Emily Shaffer
2021-10-15  9:43 ` [PATCH v2 05/13] hooks: convert 'post-checkout' hook to hook library Ævar Arnfjörð Bjarmason
2021-10-15 16:25   ` Emily Shaffer
2021-10-15  9:43 ` [PATCH v2 06/13] merge: convert post-merge to use hook.h Ævar Arnfjörð Bjarmason
2021-10-15 16:26   ` Emily Shaffer
2021-10-15  9:43 ` [PATCH v2 07/13] git hook run: add an --ignore-missing flag Ævar Arnfjörð Bjarmason
2021-10-15 16:26   ` Emily Shaffer
2021-10-15  9:43 ` [PATCH v2 08/13] send-email: use 'git hook run' for 'sendemail-validate' Ævar Arnfjörð Bjarmason
2021-10-15 17:07   ` Emily Shaffer
2021-10-15  9:43 ` [PATCH v2 09/13] git-p4: use 'git hook' to run hooks Ævar Arnfjörð Bjarmason
2021-10-15 17:08   ` Emily Shaffer
2021-10-15  9:43 ` [PATCH v2 10/13] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h Ævar Arnfjörð Bjarmason
2021-10-15 17:15   ` Emily Shaffer
2021-10-15  9:43 ` [PATCH v2 11/13] read-cache: convert post-index-change to use hook.h Ævar Arnfjörð Bjarmason
2021-10-15 17:17   ` Emily Shaffer
2021-10-15  9:43 ` [PATCH v2 12/13] receive-pack: convert push-to-checkout hook to hook.h Ævar Arnfjörð Bjarmason
2021-10-15  9:43 ` [PATCH v2 13/13] run-command: remove old run_hook_{le,ve}() hook API Ævar Arnfjörð Bjarmason
2021-10-15 17:18   ` Emily Shaffer
2021-10-15 16:23 ` [PATCH v2 00/13] hook.[ch]: new library to run hooks + simple hook conversion Emily Shaffer
2021-10-16  0:58 ` Junio C Hamano
2021-10-16  5:22   ` Eric Sunshine
2021-10-16  5:47   ` Ævar Arnfjörð Bjarmason
2021-10-16 18:04     ` Junio C Hamano
2021-10-16 18:10       ` Ævar Arnfjörð Bjarmason
2021-10-19 23:20 ` [PATCH v3 " Ævar Arnfjörð Bjarmason
2021-10-19 23:20   ` [PATCH v3 01/13] hook: add 'run' subcommand Ævar Arnfjörð Bjarmason
2021-10-19 23:20   ` [PATCH v3 02/13] gc: use hook library for pre-auto-gc hook Ævar Arnfjörð Bjarmason
2021-10-19 23:20   ` [PATCH v3 03/13] rebase: convert pre-rebase to use hook.h Ævar Arnfjörð Bjarmason
2021-10-29  5:48     ` Junio C Hamano
2021-10-19 23:20   ` [PATCH v3 04/13] am: convert applypatch " Ævar Arnfjörð Bjarmason
2021-10-19 23:20   ` [PATCH v3 05/13] hooks: convert 'post-checkout' hook to hook library Ævar Arnfjörð Bjarmason
2021-10-29  5:55     ` Junio C Hamano
2021-10-19 23:20   ` [PATCH v3 06/13] merge: convert post-merge to use hook.h Ævar Arnfjörð Bjarmason
2021-10-19 23:20   ` [PATCH v3 07/13] git hook run: add an --ignore-missing flag Ævar Arnfjörð Bjarmason
2021-10-29  5:59     ` Junio C Hamano
2021-10-19 23:20   ` [PATCH v3 08/13] send-email: use 'git hook run' for 'sendemail-validate' Ævar Arnfjörð Bjarmason
2021-10-19 23:20   ` [PATCH v3 09/13] git-p4: use 'git hook' to run hooks Ævar Arnfjörð Bjarmason
2021-10-19 23:20   ` [PATCH v3 10/13] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h Ævar Arnfjörð Bjarmason
2021-10-19 23:20   ` [PATCH v3 11/13] read-cache: convert post-index-change to use hook.h Ævar Arnfjörð Bjarmason
2021-10-19 23:20   ` [PATCH v3 12/13] receive-pack: convert push-to-checkout hook to hook.h Ævar Arnfjörð Bjarmason
2021-10-19 23:20   ` [PATCH v3 13/13] run-command: remove old run_hook_{le,ve}() hook API Ævar Arnfjörð Bjarmason
2021-10-29  6:27   ` [PATCH v3 00/13] hook.[ch]: new library to run hooks + simple hook conversion Junio C Hamano
2021-11-01 18:56   ` [PATCH v4 00/17] " Ævar Arnfjörð Bjarmason
2021-11-01 18:56     ` [PATCH v4 01/17] hook: add 'run' subcommand Ævar Arnfjörð Bjarmason
2021-11-01 18:56     ` [PATCH v4 02/17] hook API: add a run_hooks() wrapper Ævar Arnfjörð Bjarmason
2021-11-01 18:56     ` [PATCH v4 03/17] gc: use hook library for pre-auto-gc hook Ævar Arnfjörð Bjarmason
2021-11-01 18:56     ` [PATCH v4 04/17] am: convert {pre,post}-applypatch to use hook.h Ævar Arnfjörð Bjarmason
2021-11-01 18:56     ` [PATCH v4 05/17] hook API: add a run_hooks_l() wrapper Ævar Arnfjörð Bjarmason
2021-11-01 18:56     ` [PATCH v4 06/17] rebase: convert pre-rebase to use hook.h Ævar Arnfjörð Bjarmason
2021-11-01 18:56     ` [PATCH v4 07/17] am: convert applypatch-msg " Ævar Arnfjörð Bjarmason
2021-11-01 18:56     ` [PATCH v4 08/17] merge: convert post-merge " Ævar Arnfjörð Bjarmason
2021-11-01 18:56     ` [PATCH v4 09/17] hooks: convert non-worktree 'post-checkout' hook to hook library Ævar Arnfjörð Bjarmason
2021-11-01 18:56     ` [PATCH v4 10/17] hooks: convert worktree " Ævar Arnfjörð Bjarmason
2021-11-01 18:56     ` [PATCH v4 11/17] git hook run: add an --ignore-missing flag Ævar Arnfjörð Bjarmason
2021-11-01 18:56     ` [PATCH v4 12/17] send-email: use 'git hook run' for 'sendemail-validate' Ævar Arnfjörð Bjarmason
2021-11-01 18:56     ` [PATCH v4 13/17] git-p4: use 'git hook' to run hooks Ævar Arnfjörð Bjarmason
2021-11-01 18:56     ` [PATCH v4 14/17] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h Ævar Arnfjörð Bjarmason
2021-11-01 18:56     ` [PATCH v4 15/17] read-cache: convert post-index-change to use hook.h Ævar Arnfjörð Bjarmason
2021-11-01 18:56     ` [PATCH v4 16/17] receive-pack: convert push-to-checkout hook to hook.h Ævar Arnfjörð Bjarmason
2021-11-01 18:56     ` [PATCH v4 17/17] run-command: remove old run_hook_{le,ve}() hook API Ævar Arnfjörð Bjarmason
2021-11-23 11:45     ` [PATCH v5 00/17] hook.[ch]: new library to run hooks + simple hook conversion Ævar Arnfjörð Bjarmason
2021-11-23 11:46       ` [PATCH v5 01/17] hook: add 'run' subcommand Ævar Arnfjörð Bjarmason
2021-11-23 11:46       ` [PATCH v5 02/17] hook API: add a run_hooks() wrapper Ævar Arnfjörð Bjarmason
2021-11-23 11:46       ` [PATCH v5 03/17] gc: use hook library for pre-auto-gc hook Ævar Arnfjörð Bjarmason
2021-11-23 11:46       ` [PATCH v5 04/17] am: convert {pre,post}-applypatch to use hook.h Ævar Arnfjörð Bjarmason
2021-11-23 11:46       ` [PATCH v5 05/17] hook API: add a run_hooks_l() wrapper Ævar Arnfjörð Bjarmason
2021-11-23 11:46       ` [PATCH v5 06/17] rebase: convert pre-rebase to use hook.h Ævar Arnfjörð Bjarmason
2021-11-23 11:46       ` [PATCH v5 07/17] am: convert applypatch-msg " Ævar Arnfjörð Bjarmason
2021-11-23 11:46       ` [PATCH v5 08/17] merge: convert post-merge " Ævar Arnfjörð Bjarmason
2021-11-23 11:46       ` [PATCH v5 09/17] hooks: convert non-worktree 'post-checkout' hook to hook library Ævar Arnfjörð Bjarmason
2021-11-23 11:46       ` [PATCH v5 10/17] hooks: convert worktree " Ævar Arnfjörð Bjarmason
2021-11-23 11:46       ` [PATCH v5 11/17] git hook run: add an --ignore-missing flag Ævar Arnfjörð Bjarmason
2021-11-23 11:46       ` [PATCH v5 12/17] send-email: use 'git hook run' for 'sendemail-validate' Ævar Arnfjörð Bjarmason
2021-11-23 11:46       ` [PATCH v5 13/17] git-p4: use 'git hook' to run hooks Ævar Arnfjörð Bjarmason
2021-11-23 11:46       ` [PATCH v5 14/17] commit: convert {pre-commit,prepare-commit-msg} hook to hook.h Ævar Arnfjörð Bjarmason
2021-11-23 11:46       ` [PATCH v5 15/17] read-cache: convert post-index-change to use hook.h Ævar Arnfjörð Bjarmason
2021-11-23 11:46       ` [PATCH v5 16/17] receive-pack: convert push-to-checkout hook to hook.h Ævar Arnfjörð Bjarmason
2021-11-23 11:46       ` [PATCH v5 17/17] run-command: remove old run_hook_{le,ve}() hook API Ævar Arnfjörð Bjarmason

Code repositories for project(s) associated with this inbox:

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).