From c14c3959edd605db06c99fe868177548e6008ec0 Mon Sep 17 00:00:00 2001 From: Tyrie Vella Date: Wed, 21 Oct 2026 11:01:45 -0700 Subject: [PATCH] hook: reset path before early hook lookup find_hook() keeps the resolved path in a static strbuf. If the repository has no gitdir yet, it calls hook_path_early(). That function appends the hooks directory and the hook name to the buffer, but it does not reset the buffer first. In v2.53.0.vfs.0.0, find_hook() called strbuf_reset() before either branch. The rebase onto v2.54.0, which includes upstream b51e238ddf8 (hook: allow out-of-repo 'git hook' invocations), rewrote find_hook() and dropped that reset. Because of this, a process that finds the pre-command hook before the gitdir is set up cannot find post-command later in the same process. The second lookup appends to the first path, so access() fails and the hook is skipped. This affects dashed external commands, built-ins that do not set up the repository (for example 'git version'), and the case where a dashed external is not found and Git falls back to an alias. VFS for Git takes a lock in pre-command and releases it in post-command. If an external disappears between pre-command and dispatch, Git falls back to the alias and starts a child Git process. The parent never runs post-command, so the child waits for the parent's lock and the parent waits for the child. Reset the buffer before either branch. Add tests that check the pre-command and post-command order for a dashed external command, for 'git version', and for a dashed external that disappears before the alias fallback. Assisted-by: Claude Opus 5.5 Signed-off-by: Tyrie Vella --- hook.c | 1 + t/t0401-post-command-hook.sh | 61 ++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/hook.c b/hook.c index 4b1b21839d265c..47e5b19b9783e3 100644 --- a/hook.c +++ b/hook.c @@ -81,6 +81,7 @@ const char *find_hook(struct repository *r, const char *name) int found_hook; + strbuf_reset(&path); if (!r || !r->gitdir) { if (!hook_path_early(name, &path)) return NULL; diff --git a/t/t0401-post-command-hook.sh b/t/t0401-post-command-hook.sh index 41cbbbc2c6ba10..8c33835f4d434b 100755 --- a/t/t0401-post-command-hook.sh +++ b/t/t0401-post-command-hook.sh @@ -46,6 +46,67 @@ test_expect_success 'with failing pre-command hook' ' test_path_is_missing "$(cat .git/post-command.out)" ' +write_order_hooks () { + write_script .git/hooks/pre-command <<-EOF && + echo "pre \$1" >>"$PWD/order.log" + if test "\$1" = git-vanish + then + rm -f "$PWD/bin/git-vanish" + fi + EOF + write_script .git/hooks/post-command <<-EOF + echo "post \$1" >>"$PWD/order.log" + EOF +} + +test_expect_success 'post-command runs for a dashed external command' ' + test_when_finished "rm -rf bin order.log .git/hooks/pre-command .git/hooks/post-command" && + test_config postCommand.strategy always && + write_order_hooks && + mkdir bin && + write_script bin/git-hello <<-\EOF && + echo hello + EOF + PATH="$PWD/bin:$PATH" git hello && + cat >expect <<-\EOF && + pre git-hello + post git-hello + EOF + test_cmp expect order.log +' + +test_expect_success 'post-command runs for a builtin that does not set up the repository' ' + test_when_finished "rm -f order.log .git/hooks/pre-command .git/hooks/post-command" && + test_config postCommand.strategy always && + write_order_hooks && + git version && + cat >expect <<-\EOF && + pre version + post version + EOF + test_cmp expect order.log +' + +test_expect_success 'post-command runs when a dashed external vanishes before alias fallback' ' + test_when_finished "rm -rf bin order.log .git/hooks/pre-command .git/hooks/post-command" && + test_config postCommand.strategy always && + test_config alias.vanish "rev-parse --git-dir" && + write_order_hooks && + mkdir bin && + write_script bin/git-vanish <<-\EOF && + echo vanish-script-ran + EOF + PATH="$PWD/bin:$PATH" git vanish >out && + test_grep ! vanish-script-ran out && + cat >expect <<-\EOF && + pre git-vanish + post git-vanish + pre rev-parse + post rev-parse + EOF + test_cmp expect order.log +' + test_expect_success 'with post-index-change config' ' mkdir -p internal-hooks && write_script internal-hooks/post-command <<-EOF &&