@@ -268,10 +268,20 @@ test_expect_success 'git -c core.hooksPath=<PATH> hook run' '
268268'
269269
270270test_hook_tty () {
271- cat > expect << -\EOF
272- STDOUT TTY
273- STDERR TTY
274- EOF
271+ expect_tty=$1
272+ shift
273+
274+ if test " $expect_tty " ! = " no_tty" ; then
275+ cat > expect << -\EOF
276+ STDOUT TTY
277+ STDERR TTY
278+ EOF
279+ else
280+ cat > expect << -\EOF
281+ STDOUT NO TTY
282+ STDERR NO TTY
283+ EOF
284+ fi
275285
276286 test_when_finished " rm -rf repo" &&
277287 git init repo &&
@@ -289,12 +299,21 @@ test_hook_tty () {
289299 test_cmp expect repo/actual
290300}
291301
292- test_expect_success TTY ' git hook run: stdout and stderr are connected to a TTY' '
293- test_hook_tty hook run pre-commit
302+ test_expect_success TTY ' git hook run -j1: stdout and stderr are connected to a TTY' '
303+ # hooks running sequentially (-j1) are always connected to the tty for
304+ # optimum real-time performance.
305+ test_hook_tty tty hook run -j1 pre-commit
306+ '
307+
308+ test_expect_success TTY ' git hook run -jN: stdout and stderr are not connected to a TTY' '
309+ # Hooks are not connected to the tty when run in parallel, instead they
310+ # output to a pipe through which run-command collects and de-interlaces
311+ # their outputs, which then gets passed either to the tty or a sideband.
312+ test_hook_tty no_tty hook run -j2 pre-commit
294313'
295314
296315test_expect_success TTY ' git commit: stdout and stderr are connected to a TTY' '
297- test_hook_tty commit -m"B.new"
316+ test_hook_tty tty commit -m"B.new"
298317'
299318
300319test_expect_success ' git hook list orders by config order' '
@@ -709,6 +728,108 @@ test_expect_success 'server push-to-checkout hook expects stdout redirected to s
709728 check_stdout_merged_to_stderr push-to-checkout
710729'
711730
731+ test_expect_success ' parallel hook output is not interleaved' '
732+ test_when_finished "rm -rf .git/hooks" &&
733+
734+ write_script .git/hooks/test-hook <<-EOF &&
735+ echo "Hook 1 Start"
736+ sleep 1
737+ echo "Hook 1 End"
738+ EOF
739+
740+ test_config hook.hook-2.event test-hook &&
741+ test_config hook.hook-2.command \
742+ "echo \"Hook 2 Start\"; sleep 2; echo \"Hook 2 End\"" &&
743+ test_config hook.hook-2.parallel true &&
744+ test_config hook.hook-3.event test-hook &&
745+ test_config hook.hook-3.command \
746+ "echo \"Hook 3 Start\"; sleep 3; echo \"Hook 3 End\"" &&
747+ test_config hook.hook-3.parallel true &&
748+
749+ git hook run --allow-unknown-hook-name -j3 test-hook >out 2>err.parallel &&
750+
751+ # Verify Hook 1 output is grouped
752+ sed -n "/Hook 1 Start/,/Hook 1 End/p" err.parallel >hook1_out &&
753+ test_line_count = 2 hook1_out &&
754+
755+ # Verify Hook 2 output is grouped
756+ sed -n "/Hook 2 Start/,/Hook 2 End/p" err.parallel >hook2_out &&
757+ test_line_count = 2 hook2_out &&
758+
759+ # Verify Hook 3 output is grouped
760+ sed -n "/Hook 3 Start/,/Hook 3 End/p" err.parallel >hook3_out &&
761+ test_line_count = 2 hook3_out
762+ '
763+
764+ test_expect_success ' git hook run -j1 runs hooks in series' '
765+ test_when_finished "rm -rf .git/hooks" &&
766+
767+ test_config hook.series-1.event "test-hook" &&
768+ test_config hook.series-1.command "echo 1" --add &&
769+ test_config hook.series-2.event "test-hook" &&
770+ test_config hook.series-2.command "echo 2" --add &&
771+
772+ mkdir -p .git/hooks &&
773+ write_script .git/hooks/test-hook <<-EOF &&
774+ echo 3
775+ EOF
776+
777+ cat >expected <<-\EOF &&
778+ 1
779+ 2
780+ 3
781+ EOF
782+
783+ git hook run --allow-unknown-hook-name -j1 test-hook 2>actual &&
784+ test_cmp expected actual
785+ '
786+
787+ test_expect_success ' git hook run -j2 runs hooks in parallel' '
788+ test_when_finished "rm -f sentinel.started sentinel.done hook.order" &&
789+ test_when_finished "rm -rf .git/hooks" &&
790+
791+ mkdir -p .git/hooks &&
792+ write_sentinel_hook .git/hooks/test-hook &&
793+
794+ test_config hook.hook-2.event test-hook &&
795+ test_config hook.hook-2.command \
796+ "$(sentinel_detector sentinel hook.order)" &&
797+ test_config hook.hook-2.parallel true &&
798+
799+ git hook run --allow-unknown-hook-name -j2 test-hook >out 2>err &&
800+ echo parallel >expect &&
801+ test_cmp expect hook.order
802+ '
803+
804+ test_expect_success ' git hook run -j2 overrides parallel=false' '
805+ test_when_finished "rm -f sentinel.started sentinel.done hook.order" &&
806+ test_config hook.hook-1.event test-hook &&
807+ test_config hook.hook-1.command \
808+ "touch sentinel.started; sleep 2; touch sentinel.done" &&
809+ # hook-1 intentionally has no parallel=true
810+ test_config hook.hook-2.event test-hook &&
811+ test_config hook.hook-2.command \
812+ "$(sentinel_detector sentinel hook.order)" &&
813+ # hook-2 also has no parallel=true
814+
815+ # -j2 overrides parallel=false; hooks run in parallel with a warning.
816+ git hook run --allow-unknown-hook-name -j2 test-hook >out 2>err &&
817+ echo parallel >expect &&
818+ test_cmp expect hook.order
819+ '
820+
821+ test_expect_success ' git hook run -j2 warns for hooks not marked parallel=true' '
822+ test_config hook.hook-1.event test-hook &&
823+ test_config hook.hook-1.command "true" &&
824+ test_config hook.hook-2.event test-hook &&
825+ test_config hook.hook-2.command "true" &&
826+ # neither hook has parallel=true
827+
828+ git hook run --allow-unknown-hook-name -j2 test-hook >out 2>err &&
829+ grep "hook .hook-1. is not marked as parallel=true" err &&
830+ grep "hook .hook-2. is not marked as parallel=true" err
831+ '
832+
712833test_expect_success ' hook.jobs=1 config runs hooks in series' '
713834 test_when_finished "rm -f sentinel.started sentinel.done hook.order" &&
714835
0 commit comments