Re: [PATCH] git: add --no-hooks global option

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, Apr 3, 2025 at 6:38 PM Derrick Stolee via GitGitGadget
<gitgitgadget@xxxxxxxxx> wrote:
>
> From: Derrick Stolee <stolee@xxxxxxxxx>
>
> Git has several hooks which are executed during certain events as long
> as those hooks are enabled in the hooks directory (possibly moved from
> .git/hooks via the core.hooksPath config option). These are configured
> by the user, or perhaps by tooling the user has agreed to, and are not
> required to operate a Git repository.
>
> In some situations, these hooks have poor performance and expert users
> may want to skip the hooks as they don't seem to affect the current
> situation. One example is a pre-commit hook that checks for certain
> structures in the local changes, but expert users are likely to have
> done the right thing in advance.

Or a pre-push hook that runs tests which, for some reason, are very
slow to execute but which isn't properly shutdown by a normal SIGINT,
messing up my shell.

> I have come across users who have disabled hooks themselves either by
> deleting hooks (supported, safe) or setting 'core.hooksPath' to some
> bogus path (seems unsafe). The supported process is painful to swap
> between the hook-enabled scenario and the hook-disabled scenario.

In my particular case, the tool being used (husky) sets core.hooksPath
to a directory in the repo, so I can go back to using my preferred
hooks simply by unsetting core.hooksPath, which seems safe. But
swapping is still a bit painful, as mentioned.

Worse, every "npm install" in this repo re-enables the hooks, so I
have to remember to reset core.hooksPath :/ When I don't, I spend a
few frustrating minutes getting back to normal. And then I
accidentally train myself to use --no-verify often with this project,
which is IMO a worse state of affairs.

Obviously, this repo's hooks need fixed, but in the interim something
like --no-hooks seems useful to me. Not sure if I count among the
experts or not /shrug.

> To that end, add a new --no-hooks global option to allow users to
> disable hooks quickly. This option is modeled similarly to the
> --no-advice option in b79deeb554 (advice: add --no-advice global option,
> 2024-05-03). This uses a GIT_HOOKS environment variable to communicate
> to subprocesses as well as making this a backwards-compatible way for
> tools to signal that they want to disable hooks.

On a related note: with client-side hooks always being considered
optional, it seems like there ought to be a safe, sane way to turn
them off (rather than remember --no-verify for the various commands,
which is probably the safe, sane way to bypass them on a one-off
basis).

Maybe with this I'll have more ammo to convince folks that their
sanity/security/whatever checks belong in server-side hooks and
optional developer scripts rather than pre-commit hooks.

>
> The critical piece is that all hooks pass through run_hooks_opt() where
> a static int will evaluate the environment variable and store that the
> variable is initialized for faster repeated runs.
>
> Signed-off-by: Derrick Stolee <stolee@xxxxxxxxx>
> ---
>     git: add --no-hooks global option
>
>     This is hopefully a helpful feature to more than just the experts I've
>     been hearing from.
>
>     Thanks,
>
>      * Stolee
>
> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1899%2Fderrickstolee%2Fno-hooks-v1
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1899/derrickstolee/no-hooks-v1
> Pull-Request: https://github.com/gitgitgadget/git/pull/1899
>
>  Documentation/git.adoc       | 13 ++++++++++++-
>  environment.h                |  6 ++++++
>  git.c                        |  6 +++++-
>  hook.c                       |  7 +++++++
>  t/t1350-config-hooks-path.sh | 34 ++++++++++++++++++++++++++++++++++
>  5 files changed, 64 insertions(+), 2 deletions(-)
>
> diff --git a/Documentation/git.adoc b/Documentation/git.adoc
> index 743b7b00e4d..a34c8cfbe78 100644
> --- a/Documentation/git.adoc
> +++ b/Documentation/git.adoc
> @@ -14,7 +14,7 @@ SYNOPSIS
>      [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--no-lazy-fetch]
>      [--no-optional-locks] [--no-advice] [--bare] [--git-dir=<path>]
>      [--work-tree=<path>] [--namespace=<name>] [--config-env=<name>=<envvar>]
> -    <command> [<args>]
> +    [--no-hooks] <command> [<args>]
>
>  DESCRIPTION
>  -----------
> @@ -230,6 +230,12 @@ If you just want to run git as if it was started in `<path>` then use
>         linkgit:gitattributes[5]. This is equivalent to setting the
>         `GIT_ATTR_SOURCE` environment variable.
>
> +--no-hooks::
> +       Skip running local Git hooks, even if configured locally. Hooks
> +       are an opt-in feature, so be sure that you know the impact of
> +       ignoring hooks when running with this option. This is equivalent
> +       to setting `GIT_HOOKS=0` environment variable.
> +
>  GIT COMMANDS
>  ------------
>
> @@ -771,6 +777,11 @@ for further details.
>         not set, Git will choose buffered or record-oriented flushing
>         based on whether stdout appears to be redirected to a file or not.
>
> +`GIT_HOOKS`::
> +       If this Boolean environment variable is set to false, then commands
> +       will ignore any configured hooks as if the `--no-hooks` option was
> +       provided.
> +
>  `GIT_TRACE`::
>         Enables general trace messages, e.g. alias expansion, built-in
>         command execution and external command execution.
> diff --git a/environment.h b/environment.h
> index 45e690f203f..22ddf201144 100644
> --- a/environment.h
> +++ b/environment.h
> @@ -50,6 +50,12 @@
>   */
>  #define GIT_ADVICE_ENVIRONMENT "GIT_ADVICE"
>
> +/*
> + * Environment variable used to propagate the --no-hooks global option to
> + * the hooks layer and to any child processes.
> + */
> +#define GIT_HOOKS "GIT_HOOKS"
> +
>  /*
>   * Environment variable used in handshaking the wire protocol.
>   * Contains a colon ':' separated list of keys with optional values
> diff --git a/git.c b/git.c
> index 77c43595223..d7ebcf60947 100644
> --- a/git.c
> +++ b/git.c
> @@ -41,7 +41,7 @@ const char git_usage_string[] =
>            "           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--no-lazy-fetch]\n"
>            "           [--no-optional-locks] [--no-advice] [--bare] [--git-dir=<path>]\n"
>            "           [--work-tree=<path>] [--namespace=<name>] [--config-env=<name>=<envvar>]\n"
> -          "           <command> [<args>]");
> +          "           [--no-hooks] <command> [<args>]");
>
>  const char git_more_info_string[] =
>         N_("'git help -a' and 'git help -g' list available subcommands and some\n"
> @@ -349,6 +349,10 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
>                         setenv(GIT_ADVICE_ENVIRONMENT, "0", 1);
>                         if (envchanged)
>                                 *envchanged = 1;
> +               } else if (!strcmp(cmd, "--no-hooks")) {
> +                       setenv(GIT_HOOKS, "0", 1);
> +                       if (envchanged)
> +                               *envchanged = 1;
>                 } else {
>                         fprintf(stderr, _("unknown option: %s\n"), cmd);
>                         usage(git_usage_string);
> diff --git a/hook.c b/hook.c
> index b3de1048bf4..b209553d7a8 100644
> --- a/hook.c
> +++ b/hook.c
> @@ -144,6 +144,13 @@ int run_hooks_opt(struct repository *r, const char *hook_name,
>
>                 .data = &cb_data,
>         };
> +       static int do_run_hooks = -1;
> +
> +       if (do_run_hooks < 0)
> +               do_run_hooks = git_env_bool(GIT_HOOKS, 1);
> +
> +       if (!do_run_hooks)
> +               goto cleanup;
>
>         if (!options)
>                 BUG("a struct run_hooks_opt must be provided to run_hooks");
> diff --git a/t/t1350-config-hooks-path.sh b/t/t1350-config-hooks-path.sh
> index 45a04929170..4c6a0eafe4e 100755
> --- a/t/t1350-config-hooks-path.sh
> +++ b/t/t1350-config-hooks-path.sh
> @@ -48,4 +48,38 @@ test_expect_success 'core.hooksPath=/dev/null' '
>         { test /dev/null = "$value" || test nul = "$value"; }
>  '
>
> +test_expect_success '--no-hooks' '
> +       rm -f actual &&
> +       test_might_fail git config --unset core.hooksPath &&
> +
> +       write_script .git/hooks/pre-commit <<-\EOF &&
> +       echo HOOK >>actual
> +       EOF
> +
> +       echo HOOK >expect &&
> +
> +       git commit --allow-empty -m "A" &&
> +       test_cmp expect actual &&
> +
> +       git --no-hooks commit --allow-empty -m "B" &&
> +       test_cmp expect actual
> +'
> +
> +test_expect_success 'GIT_HOOKS' '
> +       rm -f actual &&
> +       test_might_fail git config --unset core.hooksPath &&
> +
> +       write_script .git/hooks/pre-commit <<-\EOF &&
> +       echo HOOK >>actual
> +       EOF
> +
> +       echo HOOK >expect &&
> +
> +       GIT_HOOKS=1 git commit --allow-empty -m "A" &&
> +       test_cmp expect actual &&
> +
> +       GIT_HOOKS=0 git commit --allow-empty -m "B" &&
> +       test_cmp expect actual
> +'
> +
>  test_done
>
> base-commit: 5b97a56fa0e7d580dc8865b73107407c9b3f0eff
> --
> gitgitgadget
>


-- 
D. Ben Knoble





[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux