From 3e43efeba31ce182860fbb91d468f76c1ef7d10c Mon Sep 17 00:00:00 2001 From: acevif Date: Thu, 9 Apr 2026 01:55:51 +0900 Subject: [PATCH 1/6] fix(hooks): prevent stdin drain from skipping subsequent hooks Add Date: Thu, 9 Apr 2026 01:57:03 +0900 Subject: [PATCH 2/6] test(hooks): add regression test for stdin-draining hook Verify that a hook running `cat` (which reads stdin) does not prevent subsequent hooks from executing. --- tests/hooks.bats | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/hooks.bats b/tests/hooks.bats index 5df9c77..211e756 100644 --- a/tests/hooks.bats +++ b/tests/hooks.bats @@ -83,6 +83,15 @@ teardown() { [ "$status" -eq 1 ] } +@test "run_hooks continues after hook that reads stdin" { + git config --add gtr.hook.postCreate 'echo first >> "$REPO_ROOT/order"' + git config --add gtr.hook.postCreate 'cat' + git config --add gtr.hook.postCreate 'echo third >> "$REPO_ROOT/order"' + run_hooks postCreate REPO_ROOT="$TEST_REPO" + [ "$(head -1 "$TEST_REPO/order")" = "first" ] + [ "$(tail -1 "$TEST_REPO/order")" = "third" ] +} + @test "run_hooks REPO_ROOT and BRANCH env vars available" { git config --add gtr.hook.postCreate 'echo "$REPO_ROOT|$BRANCH" > "$REPO_ROOT/vars"' run_hooks postCreate REPO_ROOT="$TEST_REPO" BRANCH="test-branch" From 95384e4629844810bfdd6b474caefcdd2cdd780b Mon Sep 17 00:00:00 2001 From: acevif Date: Thu, 9 Apr 2026 02:00:42 +0900 Subject: [PATCH 3/6] test(hooks): extend stdin-drain regression tests to preRemove and postRemove --- tests/hooks.bats | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/hooks.bats b/tests/hooks.bats index 211e756..ff37086 100644 --- a/tests/hooks.bats +++ b/tests/hooks.bats @@ -83,7 +83,7 @@ teardown() { [ "$status" -eq 1 ] } -@test "run_hooks continues after hook that reads stdin" { +@test "run_hooks continues after hook that reads stdin (postCreate)" { git config --add gtr.hook.postCreate 'echo first >> "$REPO_ROOT/order"' git config --add gtr.hook.postCreate 'cat' git config --add gtr.hook.postCreate 'echo third >> "$REPO_ROOT/order"' @@ -92,6 +92,24 @@ teardown() { [ "$(tail -1 "$TEST_REPO/order")" = "third" ] } +@test "run_hooks continues after hook that reads stdin (preRemove)" { + git config --add gtr.hook.preRemove 'echo first >> "$REPO_ROOT/order"' + git config --add gtr.hook.preRemove 'cat' + git config --add gtr.hook.preRemove 'echo third >> "$REPO_ROOT/order"' + run_hooks preRemove REPO_ROOT="$TEST_REPO" + [ "$(head -1 "$TEST_REPO/order")" = "first" ] + [ "$(tail -1 "$TEST_REPO/order")" = "third" ] +} + +@test "run_hooks continues after hook that reads stdin (postRemove)" { + git config --add gtr.hook.postRemove 'echo first >> "$REPO_ROOT/order"' + git config --add gtr.hook.postRemove 'cat' + git config --add gtr.hook.postRemove 'echo third >> "$REPO_ROOT/order"' + run_hooks postRemove REPO_ROOT="$TEST_REPO" + [ "$(head -1 "$TEST_REPO/order")" = "first" ] + [ "$(tail -1 "$TEST_REPO/order")" = "third" ] +} + @test "run_hooks REPO_ROOT and BRANCH env vars available" { git config --add gtr.hook.postCreate 'echo "$REPO_ROOT|$BRANCH" > "$REPO_ROOT/vars"' run_hooks postCreate REPO_ROOT="$TEST_REPO" BRANCH="test-branch" From 1a127be1ac29b5f94dc6eb7b51ff8a0a2b248d5d Mon Sep 17 00:00:00 2001 From: acevif Date: Thu, 9 Apr 2026 02:06:11 +0900 Subject: [PATCH 4/6] fix(hooks): prevent stdin drain in run_hooks_export (postCd) Add > "$REPO_ROOT/order"' + git config --add gtr.hook.postCd 'cat' + git config --add gtr.hook.postCd 'echo third >> "$REPO_ROOT/order"' + (cd "$TEST_REPO" && run_hooks_export postCd REPO_ROOT="$TEST_REPO") + [ "$(head -1 "$TEST_REPO/order")" = "first" ] + [ "$(tail -1 "$TEST_REPO/order")" = "third" ] +} From ba4171e78c0a36a8f5b8e9a89a5c5a78ba57c8ea Mon Sep 17 00:00:00 2001 From: Tom Elizaga Date: Thu, 9 Apr 2026 17:06:05 -0700 Subject: [PATCH 5/6] fix(init): isolate postCd hook stdin in generated shells --- lib/commands/init.sh | 4 ++-- tests/init.bats | 54 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/lib/commands/init.sh b/lib/commands/init.sh index a06f223..16b7109 100644 --- a/lib/commands/init.sh +++ b/lib/commands/init.sh @@ -126,7 +126,7 @@ __FUNC___run_post_cd_hooks() { [ -z "$_gtr_hook" ] && continue case "$_gtr_seen" in *"|$_gtr_hook|"*) continue ;; esac _gtr_seen="$_gtr_seen|$_gtr_hook|" - eval "$_gtr_hook" || echo "__FUNC__: postCd hook failed: $_gtr_hook" >&2 + eval "$_gtr_hook" &2 done <<< "$_gtr_hooks" unset WORKTREE_PATH REPO_ROOT BRANCH fi @@ -303,7 +303,7 @@ __FUNC___run_post_cd_hooks() { [ -z "$_gtr_hook" ] && continue case "$_gtr_seen" in *"|$_gtr_hook|"*) continue ;; esac _gtr_seen="$_gtr_seen|$_gtr_hook|" - eval "$_gtr_hook" || echo "__FUNC__: postCd hook failed: $_gtr_hook" >&2 + eval "$_gtr_hook" &2 done <<< "$_gtr_hooks" unset WORKTREE_PATH REPO_ROOT BRANCH fi diff --git a/tests/init.bats b/tests/init.bats index 1bf4726..77c5d54 100644 --- a/tests/init.bats +++ b/tests/init.bats @@ -70,6 +70,46 @@ printf 'REPLY=%s\n' "${COMPREPLY[*]}" BASH } +run_generated_post_cd_hooks() { + local shell_name="$1" + + "$shell_name" -s -- "$PROJECT_ROOT" "$shell_name" <<'SCRIPT' +PROJECT_ROOT="$1" +shell_name="$2" + +log_info() { :; } +log_warn() { :; } +log_error() { :; } +show_command_help() { :; } +compdef() { :; } + +# shellcheck disable=SC1090 +. "$PROJECT_ROOT/lib/commands/init.sh" + +repo=$(mktemp -d) +cleanup() { + rm -rf "$repo" +} +trap cleanup EXIT + +git -C "$repo" init --quiet +git -C "$repo" config user.name "Test User" +git -C "$repo" config user.email "test@example.com" +git -C "$repo" commit --allow-empty -m "init" --quiet + +git -C "$repo" config --add gtr.hook.postCd 'echo first >> "$REPO_ROOT/order"' +git -C "$repo" config --add gtr.hook.postCd 'cat' +git -C "$repo" config --add gtr.hook.postCd 'echo third >> "$REPO_ROOT/order"' + +eval "$(cmd_init "$shell_name")" +gtr_run_post_cd_hooks "$repo" + +printf 'ORDER=' +paste -sd, "$repo/order" +printf '\n' +SCRIPT +} + # ── Default function name ──────────────────────────────────────────────────── @test "bash output defines gtr() function by default" { @@ -253,6 +293,20 @@ BASH [[ "$output" == *'could not determine new directory for --cd'* ]] } +@test "bash generated postCd hooks continue after stdin read" { + run run_generated_post_cd_hooks bash + + [ "$status" -eq 0 ] + [ "$output" = "ORDER=first,third" ] +} + +@test "zsh generated postCd hooks continue after stdin read" { + run run_generated_post_cd_hooks zsh + + [ "$status" -eq 0 ] + [ "$output" = "ORDER=first,third" ] +} + @test "fish output uses worktree diff to locate the new directory for --cd" { run cmd_init fish [ "$status" -eq 0 ] From 98f73ec919f3412c1acc547ec2c1d221c9860c95 Mon Sep 17 00:00:00 2001 From: Tom Elizaga Date: Thu, 9 Apr 2026 17:14:54 -0700 Subject: [PATCH 6/6] test(init): skip runtime shell checks when shell missing --- tests/init.bats | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/init.bats b/tests/init.bats index 77c5d54..70339df 100644 --- a/tests/init.bats +++ b/tests/init.bats @@ -110,6 +110,12 @@ printf '\n' SCRIPT } +require_runtime_shell() { + local shell_name="$1" + + command -v "$shell_name" >/dev/null 2>&1 || skip "$shell_name is not installed" +} + # ── Default function name ──────────────────────────────────────────────────── @test "bash output defines gtr() function by default" { @@ -294,6 +300,7 @@ SCRIPT } @test "bash generated postCd hooks continue after stdin read" { + require_runtime_shell bash run run_generated_post_cd_hooks bash [ "$status" -eq 0 ] @@ -301,6 +308,7 @@ SCRIPT } @test "zsh generated postCd hooks continue after stdin read" { + require_runtime_shell zsh run run_generated_post_cd_hooks zsh [ "$status" -eq 0 ]