Subject: [PATCH v8 01/12] t1800: add hook output stream tests

From: Adrian Ratiu <[email-masked]>
Date: Wed, 28 Jan 2026 23:39:16 +0200

Lack of test coverage in this area led to some regressions while
converting the remaining hooks to the newer hook.[ch] API.

Add some tests to verify hooks write to the expected output streams.

Suggested-by: Patrick Steinhardt <[email-masked]>
Suggested-by: Junio C Hamano <[email-masked]>
Signed-off-by: Adrian Ratiu <[email-masked]>
---
 t/t1800-hook.sh | 137 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 137 insertions(+)

diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh
index 4feaf0d7be..ed28a2fadb 100755
--- a/t/t1800-hook.sh
+++ b/t/t1800-hook.sh
@@ -184,4 +184,141 @@ test_expect_success 'stdin to hooks' '
 	test_cmp expect actual
 '
 
+check_stdout_separate_from_stderr () {
+	for hook in "$@"
+	do
+		# Ensure hook's stdout is only in stdout, not stderr
+		test_grep "Hook $hook stdout" stdout.actual || return 1
+		test_grep ! "Hook $hook stdout" stderr.actual || return 1
+
+		# Ensure hook's stderr is only in stderr, not stdout
+		test_grep "Hook $hook stderr" stderr.actual || return 1
+		test_grep ! "Hook $hook stderr" stdout.actual || return 1
+	done
+}
+
+check_stdout_merged_to_stderr () {
+	for hook in "$@"
+	do
+		# Ensure hook's stdout is only in stderr, not stdout
+		test_grep "Hook $hook stdout" stderr.actual || return 1
+		test_grep ! "Hook $hook stdout" stdout.actual || return 1
+
+		# Ensure hook's stderr is only in stderr, not stdout
+		test_grep "Hook $hook stderr" stderr.actual || return 1
+		test_grep ! "Hook $hook stderr" stdout.actual || return 1
+	done
+}
+
+setup_hooks () {
+	for hook in "$@"
+	do
+		test_hook $hook <<-EOF
+		echo >&1 Hook $hook stdout
+		echo >&2 Hook $hook stderr
+		EOF
+	done
+}
+
+test_expect_success 'client hooks: pre-push expects separate stdout and stderr' '
+	test_when_finished "rm -f stdout.actual stderr.actual" &&
+	git init --bare remote &&
+	git remote add origin remote &&
+	test_commit A &&
+	setup_hooks pre-push &&
+	git push origin HEAD:main >stdout.actual 2>stderr.actual &&
+	check_stdout_separate_from_stderr pre-push
+'
+
+test_expect_success 'client hooks: commit hooks expect stdout redirected to stderr' '
+	hooks="pre-commit prepare-commit-msg \
+		commit-msg post-commit \
+		reference-transaction" &&
+	setup_hooks $hooks &&
+	test_when_finished "rm -f stdout.actual stderr.actual" &&
+	git checkout -B main &&
+	git checkout -b branch-a &&
+	test_commit commit-on-branch-a &&
+	git commit --allow-empty -m "Test" >stdout.actual 2>stderr.actual &&
+	check_stdout_merged_to_stderr $hooks
+'
+
+test_expect_success 'client hooks: checkout hooks expect stdout redirected to stderr' '
+	setup_hooks post-checkout reference-transaction &&
+	test_when_finished "rm -f stdout.actual stderr.actual" &&
+	git checkout -b new-branch main >stdout.actual 2>stderr.actual &&
+	check_stdout_merged_to_stderr post-checkout reference-transaction
+'
+
+test_expect_success 'client hooks: merge hooks expect stdout redirected to stderr' '
+	setup_hooks pre-merge-commit post-merge reference-transaction &&
+	test_when_finished "rm -f stdout.actual stderr.actual" &&
+	test_commit new-branch-commit &&
+	git merge --no-ff branch-a >stdout.actual 2>stderr.actual &&
+	check_stdout_merged_to_stderr pre-merge-commit post-merge reference-transaction
+'
+
+test_expect_success 'client hooks: post-rewrite hooks expect stdout redirected to stderr' '
+	setup_hooks post-rewrite reference-transaction &&
+	test_when_finished "rm -f stdout.actual stderr.actual" &&
+	git commit --amend --allow-empty --no-edit >stdout.actual 2>stderr.actual &&
+	check_stdout_merged_to_stderr post-rewrite reference-transaction
+'
+
+test_expect_success 'client hooks: applypatch hooks expect stdout redirected to stderr' '
+	setup_hooks applypatch-msg pre-applypatch post-applypatch &&
+	test_when_finished "rm -f stdout.actual stderr.actual" &&
+	git checkout -b branch-b main &&
+	test_commit branch-b &&
+	git format-patch -1 --stdout >patch &&
+	git checkout -b branch-c main &&
+	git am patch >stdout.actual 2>stderr.actual &&
+	check_stdout_merged_to_stderr applypatch-msg pre-applypatch post-applypatch
+'
+
+test_expect_success 'client hooks: rebase hooks expect stdout redirected to stderr' '
+	setup_hooks pre-rebase &&
+	test_when_finished "rm -f stdout.actual stderr.actual" &&
+	git checkout -b branch-d main &&
+	test_commit branch-d &&
+	git checkout main &&
+	test_commit diverge-main &&
+	git checkout branch-d &&
+	git rebase main >stdout.actual 2>stderr.actual &&
+	check_stdout_merged_to_stderr pre-rebase
+'
+
+test_expect_success 'client hooks: post-index-change expects stdout redirected to stderr' '
+	setup_hooks post-index-change &&
+	test_when_finished "rm -f stdout.actual stderr.actual" &&
+	oid=$(git hash-object -w --stdin </dev/null) &&
+	git update-index --add --cacheinfo 100644 $oid new-file \
+	    >stdout.actual 2>stderr.actual &&
+	check_stdout_merged_to_stderr post-index-change
+'
+
+test_expect_success 'server hooks expect stdout redirected to stderr' '
+	test_when_finished "rm -f stdout.actual stderr.actual" &&
+	git init --bare remote-server &&
+	git remote add origin-server remote-server &&
+	cd remote-server &&
+	setup_hooks pre-receive update post-receive post-update &&
+	cd .. &&
+	git push origin-server HEAD:new-branch >stdout.actual 2>stderr.actual &&
+	check_stdout_merged_to_stderr pre-receive update post-receive post-update
+'
+
+test_expect_success 'server push-to-checkout hook expects stdout redirected to stderr' '
+	test_when_finished "rm -f stdout.actual stderr.actual" &&
+	git init server &&
+	git -C server checkout -b main &&
+	test_config -C server receive.denyCurrentBranch updateInstead &&
+	git remote add origin-server-2 server &&
+	cd server &&
+	setup_hooks push-to-checkout &&
+	cd .. &&
+	git push origin-server-2 HEAD:main >stdout.actual 2>stderr.actual &&
+	check_stdout_merged_to_stderr push-to-checkout
+'
+
 test_done
-- 
2.52.0.732.gb351b5166d.dirty