@@ -239,10 +239,20 @@ test_expect_success 'git -c core.hooksPath=<PATH> hook run' '
239239'
240240
241241test_hook_tty () {
242- cat > expect << -\EOF
243- STDOUT TTY
244- STDERR TTY
245- EOF
242+ expect_tty=$1
243+ shift
244+
245+ if test " $expect_tty " ! = " no_tty" ; then
246+ cat > expect << -\EOF
247+ STDOUT TTY
248+ STDERR TTY
249+ EOF
250+ else
251+ cat > expect << -\EOF
252+ STDOUT NO TTY
253+ STDERR NO TTY
254+ EOF
255+ fi
246256
247257 test_when_finished " rm -rf repo" &&
248258 git init repo &&
@@ -260,12 +270,21 @@ test_hook_tty () {
260270 test_cmp expect repo/actual
261271}
262272
263- test_expect_success TTY ' git hook run: stdout and stderr are connected to a TTY' '
264- test_hook_tty hook run pre-commit
273+ test_expect_success TTY ' git hook run -j1: stdout and stderr are connected to a TTY' '
274+ # hooks running sequentially (-j1) are always connected to the tty for
275+ # optimum real-time performance.
276+ test_hook_tty tty hook run -j1 pre-commit
277+ '
278+
279+ test_expect_success TTY ' git hook run -jN: stdout and stderr are not connected to a TTY' '
280+ # Hooks are not connected to the tty when run in parallel, instead they
281+ # output to a pipe through which run-command collects and de-interlaces
282+ # their outputs, which then gets passed either to the tty or a sideband.
283+ test_hook_tty no_tty hook run -j2 pre-commit
265284'
266285
267286test_expect_success TTY ' git commit: stdout and stderr are connected to a TTY' '
268- test_hook_tty commit -m"B.new"
287+ test_hook_tty tty commit -m"B.new"
269288'
270289
271290test_expect_success ' git hook list orders by config order' '
@@ -604,6 +623,96 @@ test_expect_success 'server push-to-checkout hook expects stdout redirected to s
604623 check_stdout_merged_to_stderr push-to-checkout
605624'
606625
626+ test_expect_success ' parallel hook output is not interleaved' '
627+ test_when_finished "rm -rf .git/hooks" &&
628+
629+ write_script .git/hooks/test-hook <<-EOF &&
630+ echo "Hook 1 Start"
631+ sleep 1
632+ echo "Hook 1 End"
633+ EOF
634+
635+ test_config hook.hook-2.event test-hook &&
636+ test_config hook.hook-2.command \
637+ "echo \"Hook 2 Start\"; sleep 2; echo \"Hook 2 End\"" &&
638+ test_config hook.hook-2.parallel true &&
639+ test_config hook.hook-3.event test-hook &&
640+ test_config hook.hook-3.command \
641+ "echo \"Hook 3 Start\"; sleep 3; echo \"Hook 3 End\"" &&
642+ test_config hook.hook-3.parallel true &&
643+
644+ git hook run -j3 test-hook >out 2>err.parallel &&
645+
646+ # Verify Hook 1 output is grouped
647+ sed -n "/Hook 1 Start/,/Hook 1 End/p" err.parallel >hook1_out &&
648+ test_line_count = 2 hook1_out &&
649+
650+ # Verify Hook 2 output is grouped
651+ sed -n "/Hook 2 Start/,/Hook 2 End/p" err.parallel >hook2_out &&
652+ test_line_count = 2 hook2_out &&
653+
654+ # Verify Hook 3 output is grouped
655+ sed -n "/Hook 3 Start/,/Hook 3 End/p" err.parallel >hook3_out &&
656+ test_line_count = 2 hook3_out
657+ '
658+
659+ test_expect_success ' git hook run -j1 runs hooks in series' '
660+ test_when_finished "rm -rf .git/hooks" &&
661+
662+ test_config hook.series-1.event "test-hook" &&
663+ test_config hook.series-1.command "echo 1" --add &&
664+ test_config hook.series-2.event "test-hook" &&
665+ test_config hook.series-2.command "echo 2" --add &&
666+
667+ mkdir -p .git/hooks &&
668+ write_script .git/hooks/test-hook <<-EOF &&
669+ echo 3
670+ EOF
671+
672+ cat >expected <<-\EOF &&
673+ 1
674+ 2
675+ 3
676+ EOF
677+
678+ git hook run -j1 test-hook 2>actual &&
679+ test_cmp expected actual
680+ '
681+
682+ test_expect_success ' git hook run -j2 runs hooks in parallel' '
683+ test_when_finished "rm -f sentinel.started sentinel.done hook.order" &&
684+ test_when_finished "rm -rf .git/hooks" &&
685+
686+ mkdir -p .git/hooks &&
687+ write_sentinel_hook .git/hooks/test-hook &&
688+
689+ test_config hook.hook-2.event test-hook &&
690+ test_config hook.hook-2.command \
691+ "$(sentinel_detector sentinel hook.order)" &&
692+ test_config hook.hook-2.parallel true &&
693+
694+ git hook run -j2 test-hook >out 2>err &&
695+ echo parallel >expect &&
696+ test_cmp expect hook.order
697+ '
698+
699+ test_expect_success ' git hook run -j2 is blocked by parallel=false' '
700+ test_when_finished "rm -f sentinel.started sentinel.done hook.order" &&
701+ test_config hook.hook-1.event test-hook &&
702+ test_config hook.hook-1.command \
703+ "touch sentinel.started; sleep 2; touch sentinel.done" &&
704+ # hook-1 intentionally has no parallel=true
705+ test_config hook.hook-2.event test-hook &&
706+ test_config hook.hook-2.command \
707+ "$(sentinel_detector sentinel hook.order)" &&
708+ # hook-2 also has no parallel=true
709+
710+ # -j2 must not override parallel=false on configured hooks.
711+ git hook run -j2 test-hook >out 2>err &&
712+ echo serial >expect &&
713+ test_cmp expect hook.order
714+ '
715+
607716test_expect_success ' hook.jobs=1 config runs hooks in series' '
608717 test_when_finished "rm -f sentinel.started sentinel.done hook.order" &&
609718
0 commit comments