mirror of
https://github.com/git/git.git
synced 2026-03-04 14:37:35 +01:00
hook: add -j/--jobs option to git hook run
Expose the parallel job count as a command-line flag so callers can request parallelism without relying only on the hook.jobs config. Add tests covering serial/parallel execution and TTY behaviour under -j1 vs -jN. Signed-off-by: Emily Shaffer <emilyshaffer@google.com> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
committed by
Junio C Hamano
parent
4744a38c6b
commit
267b7e560b
@@ -8,7 +8,8 @@ git-hook - Run git hooks
|
||||
SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'git hook' run [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]
|
||||
'git hook' run [--ignore-missing] [--to-stdin=<path>] [(-j|--jobs) <n>]
|
||||
<hook-name> [-- <hook-args>]
|
||||
'git hook' list [-z] <hook-name>
|
||||
|
||||
DESCRIPTION
|
||||
@@ -134,6 +135,18 @@ OPTIONS
|
||||
-z::
|
||||
Terminate "list" output lines with NUL instead of newlines.
|
||||
|
||||
-j::
|
||||
--jobs::
|
||||
Only valid for `run`.
|
||||
+
|
||||
Specify how many hooks to run simultaneously. If this flag is not specified,
|
||||
the value of the `hook.jobs` config is used, see linkgit:git-config[1]. If
|
||||
neither is specified, defaults to 1 (serial execution). Some hooks always run
|
||||
sequentially regardless of this flag or the `hook.jobs` config, because git
|
||||
knows they cannot safely run in parallel: `applypatch-msg`, `pre-commit`,
|
||||
`prepare-commit-msg`, `commit-msg`, `post-commit`, `post-checkout`, and
|
||||
`push-to-checkout`.
|
||||
|
||||
WRAPPERS
|
||||
--------
|
||||
|
||||
@@ -156,7 +169,8 @@ running:
|
||||
git hook run mywrapper-start-tests \
|
||||
# providing something to stdin
|
||||
--stdin some-tempfile-123 \
|
||||
# execute hooks in serial
|
||||
# execute multiple hooks in parallel
|
||||
--jobs 3 \
|
||||
# plus some arguments of your own...
|
||||
-- \
|
||||
--testname bar \
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
#include "abspath.h"
|
||||
|
||||
#define BUILTIN_HOOK_RUN_USAGE \
|
||||
N_("git hook run [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]")
|
||||
N_("git hook run [--ignore-missing] [--to-stdin=<path>] [(-j|--jobs) <n>]\n" \
|
||||
"<hook-name> [-- <hook-args>]")
|
||||
#define BUILTIN_HOOK_LIST_USAGE \
|
||||
N_("git hook list [-z] <hook-name>")
|
||||
|
||||
@@ -97,6 +98,8 @@ static int run(int argc, const char **argv, const char *prefix,
|
||||
N_("silently ignore missing requested <hook-name>")),
|
||||
OPT_STRING(0, "to-stdin", &opt.path_to_stdin, N_("path"),
|
||||
N_("file to read into hooks' stdin")),
|
||||
OPT_UNSIGNED('j', "jobs", &opt.jobs,
|
||||
N_("run up to <n> hooks simultaneously")),
|
||||
OPT_END(),
|
||||
};
|
||||
int ret;
|
||||
|
||||
14
hook.c
14
hook.c
@@ -524,15 +524,17 @@ static unsigned int get_hook_jobs(struct repository *r,
|
||||
if (!options->stdout_to_stderr)
|
||||
return 1;
|
||||
|
||||
/* An explicit job count (FORCE_SERIAL jobs=1, or -j from CLI). */
|
||||
if (options->jobs)
|
||||
return options->jobs;
|
||||
/* Pinned serial: FORCE_SERIAL (internal) or explicit -j1 from CLI. */
|
||||
if (options->jobs == 1)
|
||||
return 1;
|
||||
|
||||
/*
|
||||
* Use hook.jobs from the already-parsed config cache (in-repo), or
|
||||
* fall back to a direct config lookup (out-of-repo). Default to 1.
|
||||
* Resolve effective job count: -jN (when given) overrides config.
|
||||
* Default to 1 when both config an -jN are missing.
|
||||
*/
|
||||
if (r && r->gitdir && r->hook_config_cache)
|
||||
if (options->jobs > 1)
|
||||
jobs = options->jobs;
|
||||
else if (r && r->gitdir && r->hook_config_cache)
|
||||
/* Use the already-parsed cache (in-repo) */
|
||||
jobs = r->hook_config_cache->jobs ? r->hook_config_cache->jobs : 1;
|
||||
else
|
||||
|
||||
123
t/t1800-hook.sh
123
t/t1800-hook.sh
@@ -239,10 +239,20 @@ test_expect_success 'git -c core.hooksPath=<PATH> hook run' '
|
||||
'
|
||||
|
||||
test_hook_tty () {
|
||||
cat >expect <<-\EOF
|
||||
STDOUT TTY
|
||||
STDERR TTY
|
||||
EOF
|
||||
expect_tty=$1
|
||||
shift
|
||||
|
||||
if test "$expect_tty" != "no_tty"; then
|
||||
cat >expect <<-\EOF
|
||||
STDOUT TTY
|
||||
STDERR TTY
|
||||
EOF
|
||||
else
|
||||
cat >expect <<-\EOF
|
||||
STDOUT NO TTY
|
||||
STDERR NO TTY
|
||||
EOF
|
||||
fi
|
||||
|
||||
test_when_finished "rm -rf repo" &&
|
||||
git init repo &&
|
||||
@@ -260,12 +270,21 @@ test_hook_tty () {
|
||||
test_cmp expect repo/actual
|
||||
}
|
||||
|
||||
test_expect_success TTY 'git hook run: stdout and stderr are connected to a TTY' '
|
||||
test_hook_tty hook run pre-commit
|
||||
test_expect_success TTY 'git hook run -j1: stdout and stderr are connected to a TTY' '
|
||||
# hooks running sequentially (-j1) are always connected to the tty for
|
||||
# optimum real-time performance.
|
||||
test_hook_tty tty hook run -j1 pre-commit
|
||||
'
|
||||
|
||||
test_expect_success TTY 'git hook run -jN: stdout and stderr are not connected to a TTY' '
|
||||
# Hooks are not connected to the tty when run in parallel, instead they
|
||||
# output to a pipe through which run-command collects and de-interlaces
|
||||
# their outputs, which then gets passed either to the tty or a sideband.
|
||||
test_hook_tty no_tty hook run -j2 pre-commit
|
||||
'
|
||||
|
||||
test_expect_success TTY 'git commit: stdout and stderr are connected to a TTY' '
|
||||
test_hook_tty commit -m"B.new"
|
||||
test_hook_tty tty commit -m"B.new"
|
||||
'
|
||||
|
||||
test_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
|
||||
check_stdout_merged_to_stderr push-to-checkout
|
||||
'
|
||||
|
||||
test_expect_success 'parallel hook output is not interleaved' '
|
||||
test_when_finished "rm -rf .git/hooks" &&
|
||||
|
||||
write_script .git/hooks/test-hook <<-EOF &&
|
||||
echo "Hook 1 Start"
|
||||
sleep 1
|
||||
echo "Hook 1 End"
|
||||
EOF
|
||||
|
||||
test_config hook.hook-2.event test-hook &&
|
||||
test_config hook.hook-2.command \
|
||||
"echo \"Hook 2 Start\"; sleep 2; echo \"Hook 2 End\"" &&
|
||||
test_config hook.hook-2.parallel true &&
|
||||
test_config hook.hook-3.event test-hook &&
|
||||
test_config hook.hook-3.command \
|
||||
"echo \"Hook 3 Start\"; sleep 3; echo \"Hook 3 End\"" &&
|
||||
test_config hook.hook-3.parallel true &&
|
||||
|
||||
git hook run -j3 test-hook >out 2>err.parallel &&
|
||||
|
||||
# Verify Hook 1 output is grouped
|
||||
sed -n "/Hook 1 Start/,/Hook 1 End/p" err.parallel >hook1_out &&
|
||||
test_line_count = 2 hook1_out &&
|
||||
|
||||
# Verify Hook 2 output is grouped
|
||||
sed -n "/Hook 2 Start/,/Hook 2 End/p" err.parallel >hook2_out &&
|
||||
test_line_count = 2 hook2_out &&
|
||||
|
||||
# Verify Hook 3 output is grouped
|
||||
sed -n "/Hook 3 Start/,/Hook 3 End/p" err.parallel >hook3_out &&
|
||||
test_line_count = 2 hook3_out
|
||||
'
|
||||
|
||||
test_expect_success 'git hook run -j1 runs hooks in series' '
|
||||
test_when_finished "rm -rf .git/hooks" &&
|
||||
|
||||
test_config hook.series-1.event "test-hook" &&
|
||||
test_config hook.series-1.command "echo 1" --add &&
|
||||
test_config hook.series-2.event "test-hook" &&
|
||||
test_config hook.series-2.command "echo 2" --add &&
|
||||
|
||||
mkdir -p .git/hooks &&
|
||||
write_script .git/hooks/test-hook <<-EOF &&
|
||||
echo 3
|
||||
EOF
|
||||
|
||||
cat >expected <<-\EOF &&
|
||||
1
|
||||
2
|
||||
3
|
||||
EOF
|
||||
|
||||
git hook run -j1 test-hook 2>actual &&
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success 'git hook run -j2 runs hooks in parallel' '
|
||||
test_when_finished "rm -f sentinel.started sentinel.done hook.order" &&
|
||||
test_when_finished "rm -rf .git/hooks" &&
|
||||
|
||||
mkdir -p .git/hooks &&
|
||||
write_sentinel_hook .git/hooks/test-hook &&
|
||||
|
||||
test_config hook.hook-2.event test-hook &&
|
||||
test_config hook.hook-2.command \
|
||||
"$(sentinel_detector sentinel hook.order)" &&
|
||||
test_config hook.hook-2.parallel true &&
|
||||
|
||||
git hook run -j2 test-hook >out 2>err &&
|
||||
echo parallel >expect &&
|
||||
test_cmp expect hook.order
|
||||
'
|
||||
|
||||
test_expect_success 'git hook run -j2 is blocked by parallel=false' '
|
||||
test_when_finished "rm -f sentinel.started sentinel.done hook.order" &&
|
||||
test_config hook.hook-1.event test-hook &&
|
||||
test_config hook.hook-1.command \
|
||||
"touch sentinel.started; sleep 2; touch sentinel.done" &&
|
||||
# hook-1 intentionally has no parallel=true
|
||||
test_config hook.hook-2.event test-hook &&
|
||||
test_config hook.hook-2.command \
|
||||
"$(sentinel_detector sentinel hook.order)" &&
|
||||
# hook-2 also has no parallel=true
|
||||
|
||||
# -j2 must not override parallel=false on configured hooks.
|
||||
git hook run -j2 test-hook >out 2>err &&
|
||||
echo serial >expect &&
|
||||
test_cmp expect hook.order
|
||||
'
|
||||
|
||||
test_expect_success 'hook.jobs=1 config runs hooks in series' '
|
||||
test_when_finished "rm -f sentinel.started sentinel.done hook.order" &&
|
||||
|
||||
|
||||
Reference in New Issue
Block a user