Skip to content

Commit 5c58dbc

Browse files
10ne1gitster
authored andcommitted
hook: reject unknown hook names in git-hook(1)
Teach "git hook run" and "git hook list" to reject hook event names that are not recognized by Git. This helps catch typos such as "prereceive" when "pre-receive" was intended, since in 99% of the cases users want known (already-existing) hook names. The list of known hooks is derived from the generated hook-list.h (built from Documentation/githooks.adoc). This is why the Makefile is updated, so builtin/hook.c depends on hook-list.h. In meson the header is already a dependency for all builtins, no change required. The "--allow-unknown-hook-name" flag can be used to bypass this check. Suggested-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent e17bd99 commit 5c58dbc

4 files changed

Lines changed: 100 additions & 31 deletions

File tree

Documentation/git-hook.adoc

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ git-hook - Run git hooks
88
SYNOPSIS
99
--------
1010
[verse]
11-
'git hook' run [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]
12-
'git hook' list [-z] [--show-scope] <hook-name>
11+
'git hook' run [--allow-unknown-hook-name] [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]
12+
'git hook' list [--allow-unknown-hook-name] [-z] [--show-scope] <hook-name>
1313

1414
DESCRIPTION
1515
-----------
@@ -121,6 +121,13 @@ list [-z] [--show-scope]::
121121
OPTIONS
122122
-------
123123

124+
--allow-unknown-hook-name::
125+
By default `git hook run` and `git hook list` will bail out when
126+
`<hook-name>` is not a hook event known to Git (see linkgit:githooks[5]
127+
for the list of known hooks). This is meant to help catch typos
128+
such as `prereceive` when `pre-receive` was intended. Pass this
129+
flag to allow unknown hook names.
130+
124131
--to-stdin::
125132
For "run"; specify a file which will be streamed into the
126133
hook's stdin. The hook will receive the entire file from
@@ -159,7 +166,7 @@ Then, in your 'mywrapper' tool, you can invoke any users' configured hooks by
159166
running:
160167
161168
----
162-
git hook run mywrapper-start-tests \
169+
git hook run --allow-unknown-hook-name mywrapper-start-tests \
163170
# providing something to stdin
164171
--stdin some-tempfile-123 \
165172
# execute hooks in serial

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2663,6 +2663,7 @@ git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS)
26632663

26642664
help.sp help.s help.o: command-list.h
26652665
builtin/bugreport.sp builtin/bugreport.s builtin/bugreport.o: hook-list.h
2666+
builtin/hook.sp builtin/hook.s builtin/hook.o: hook-list.h
26662667

26672668
builtin/help.sp builtin/help.s builtin/help.o: config-list.h GIT-PREFIX
26682669
builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \

builtin/hook.c

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,22 @@
44
#include "environment.h"
55
#include "gettext.h"
66
#include "hook.h"
7+
#include "hook-list.h"
78
#include "parse-options.h"
89

910
#define BUILTIN_HOOK_RUN_USAGE \
10-
N_("git hook run [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]")
11+
N_("git hook run [--allow-unknown-hook-name] [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]")
1112
#define BUILTIN_HOOK_LIST_USAGE \
12-
N_("git hook list [-z] [--show-scope] <hook-name>")
13+
N_("git hook list [--allow-unknown-hook-name] [-z] [--show-scope] <hook-name>")
14+
15+
static int is_known_hook(const char *name)
16+
{
17+
const char **p;
18+
for (p = hook_name_list; *p; p++)
19+
if (!strcmp(*p, name))
20+
return 1;
21+
return 0;
22+
}
1323

1424
static const char * const builtin_hook_usage[] = {
1525
BUILTIN_HOOK_RUN_USAGE,
@@ -34,13 +44,16 @@ static int list(int argc, const char **argv, const char *prefix,
3444
const char *hookname = NULL;
3545
int line_terminator = '\n';
3646
int show_scope = 0;
47+
int allow_unknown = 0;
3748
int ret = 0;
3849

3950
struct option list_options[] = {
4051
OPT_SET_INT('z', NULL, &line_terminator,
4152
N_("use NUL as line terminator"), '\0'),
4253
OPT_BOOL(0, "show-scope", &show_scope,
4354
N_("show the config scope that defined each hook")),
55+
OPT_BOOL(0, "allow-unknown-hook-name", &allow_unknown,
56+
N_("allow running a hook with a non-native hook name")),
4457
OPT_END(),
4558
};
4659

@@ -57,6 +70,13 @@ static int list(int argc, const char **argv, const char *prefix,
5770

5871
hookname = argv[0];
5972

73+
if (!allow_unknown && !is_known_hook(hookname)) {
74+
error(_("unknown hook event '%s';\n"
75+
"use --allow-unknown-hook-name to allow non-native hook names"),
76+
hookname);
77+
return 1;
78+
}
79+
6080
head = list_hooks(repo, hookname, NULL);
6181

6282
if (!head->nr) {
@@ -103,8 +123,11 @@ static int run(int argc, const char **argv, const char *prefix,
103123
int i;
104124
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
105125
int ignore_missing = 0;
126+
int allow_unknown = 0;
106127
const char *hook_name;
107128
struct option run_options[] = {
129+
OPT_BOOL(0, "allow-unknown-hook-name", &allow_unknown,
130+
N_("allow running a hook with a non-native hook name")),
108131
OPT_BOOL(0, "ignore-missing", &ignore_missing,
109132
N_("silently ignore missing requested <hook-name>")),
110133
OPT_STRING(0, "to-stdin", &opt.path_to_stdin, N_("path"),
@@ -136,6 +159,14 @@ static int run(int argc, const char **argv, const char *prefix,
136159
repo_config(the_repository, git_default_config, NULL);
137160

138161
hook_name = argv[0];
162+
163+
if (!allow_unknown && !is_known_hook(hook_name)) {
164+
error(_("unknown hook event '%s';\n"
165+
"use --allow-unknown-hook-name to allow non-native hook names"),
166+
hook_name);
167+
return 1;
168+
}
169+
139170
if (!ignore_missing)
140171
opt.error_if_missing = 1;
141172
ret = run_hooks_opt(the_repository, hook_name, &opt);

t/t1800-hook.sh

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,41 @@ test_expect_success 'git hook usage' '
3131
grep "unknown option" err
3232
'
3333

34+
test_expect_success 'git hook list: unknown hook name is rejected' '
35+
test_must_fail git hook list prereceive 2>err &&
36+
test_grep "unknown hook event" err
37+
'
38+
39+
test_expect_success 'git hook run: unknown hook name is rejected' '
40+
test_must_fail git hook run prereceive 2>err &&
41+
test_grep "unknown hook event" err
42+
'
43+
44+
test_expect_success 'git hook list: known hook name is accepted' '
45+
test_must_fail git hook list pre-receive 2>err &&
46+
test_grep ! "unknown hook event" err
47+
'
48+
49+
test_expect_success 'git hook run: known hook name is accepted' '
50+
git hook run --ignore-missing pre-receive 2>err &&
51+
test_grep ! "unknown hook event" err
52+
'
53+
54+
test_expect_success 'git hook run: --allow-unknown-hook-name overrides rejection' '
55+
git hook run --allow-unknown-hook-name --ignore-missing custom-hook 2>err &&
56+
test_grep ! "unknown hook event" err
57+
'
58+
59+
test_expect_success 'git hook list: --allow-unknown-hook-name overrides rejection' '
60+
test_must_fail git hook list --allow-unknown-hook-name custom-hook 2>err &&
61+
test_grep ! "unknown hook event" err
62+
'
63+
3464
test_expect_success 'git hook list: nonexistent hook' '
3565
cat >stderr.expect <<-\EOF &&
3666
warning: no hooks found for event '\''test-hook'\''
3767
EOF
38-
test_expect_code 1 git hook list test-hook 2>stderr.actual &&
68+
test_expect_code 1 git hook list --allow-unknown-hook-name test-hook 2>stderr.actual &&
3969
test_cmp stderr.expect stderr.actual
4070
'
4171

@@ -47,7 +77,7 @@ test_expect_success 'git hook list: traditional hook from hookdir' '
4777
cat >expect <<-\EOF &&
4878
hook from hookdir
4979
EOF
50-
git hook list test-hook >actual &&
80+
git hook list --allow-unknown-hook-name test-hook >actual &&
5181
test_cmp expect actual
5282
'
5383

@@ -56,7 +86,7 @@ test_expect_success 'git hook list: configured hook' '
5686
test_config hook.myhook.event test-hook --add &&
5787
5888
echo "myhook" >expect &&
59-
git hook list test-hook >actual &&
89+
git hook list --allow-unknown-hook-name test-hook >actual &&
6090
test_cmp expect actual
6191
'
6292

@@ -68,7 +98,7 @@ test_expect_success 'git hook list: -z shows NUL-terminated output' '
6898
test_config hook.myhook.event test-hook --add &&
6999
70100
printf "myhookQhook from hookdirQ" >expect &&
71-
git hook list -z test-hook >actual.raw &&
101+
git hook list --allow-unknown-hook-name -z test-hook >actual.raw &&
72102
nul_to_q <actual.raw >actual &&
73103
test_cmp expect actual
74104
'
@@ -77,12 +107,12 @@ test_expect_success 'git hook run: nonexistent hook' '
77107
cat >stderr.expect <<-\EOF &&
78108
error: cannot find a hook named test-hook
79109
EOF
80-
test_expect_code 1 git hook run test-hook 2>stderr.actual &&
110+
test_expect_code 1 git hook run --allow-unknown-hook-name test-hook 2>stderr.actual &&
81111
test_cmp stderr.expect stderr.actual
82112
'
83113

84114
test_expect_success 'git hook run: nonexistent hook with --ignore-missing' '
85-
git hook run --ignore-missing does-not-exist 2>stderr.actual &&
115+
git hook run --allow-unknown-hook-name --ignore-missing does-not-exist 2>stderr.actual &&
86116
test_must_be_empty stderr.actual
87117
'
88118

@@ -94,7 +124,7 @@ test_expect_success 'git hook run: basic' '
94124
cat >expect <<-\EOF &&
95125
Test hook
96126
EOF
97-
git hook run test-hook 2>actual &&
127+
git hook run --allow-unknown-hook-name test-hook 2>actual &&
98128
test_cmp expect actual
99129
'
100130

@@ -108,7 +138,7 @@ test_expect_success 'git hook run: stdout and stderr both write to our stderr' '
108138
Will end up on stderr
109139
Will end up on stderr
110140
EOF
111-
git hook run test-hook >stdout.actual 2>stderr.actual &&
141+
git hook run --allow-unknown-hook-name test-hook >stdout.actual 2>stderr.actual &&
112142
test_cmp stderr.expect stderr.actual &&
113143
test_must_be_empty stdout.actual
114144
'
@@ -120,12 +150,12 @@ do
120150
exit $code
121151
EOF
122152
123-
test_expect_code $code git hook run test-hook
153+
test_expect_code $code git hook run --allow-unknown-hook-name test-hook
124154
'
125155
done
126156

127157
test_expect_success 'git hook run arg u ments without -- is not allowed' '
128-
test_expect_code 129 git hook run test-hook arg u ments
158+
test_expect_code 129 git hook run --allow-unknown-hook-name test-hook arg u ments
129159
'
130160

131161
test_expect_success 'git hook run -- pass arguments' '
@@ -139,7 +169,7 @@ test_expect_success 'git hook run -- pass arguments' '
139169
u ments
140170
EOF
141171
142-
git hook run test-hook -- arg "u ments" 2>actual &&
172+
git hook run --allow-unknown-hook-name test-hook -- arg "u ments" 2>actual &&
143173
test_cmp expect actual
144174
'
145175

@@ -148,12 +178,12 @@ test_expect_success 'git hook run: out-of-repo runs execute global hooks' '
148178
test_config_global hook.global-hook.command "echo no repo no problems" --add &&
149179
150180
echo "global-hook" >expect &&
151-
nongit git hook list test-hook >actual &&
181+
nongit git hook list --allow-unknown-hook-name test-hook >actual &&
152182
test_cmp expect actual &&
153183
154184
echo "no repo no problems" >expect &&
155185
156-
nongit git hook run test-hook 2>actual &&
186+
nongit git hook run --allow-unknown-hook-name test-hook 2>actual &&
157187
test_cmp expect actual
158188
'
159189

@@ -178,11 +208,11 @@ test_expect_success 'git -c core.hooksPath=<PATH> hook run' '
178208
# Test various ways of specifying the path. See also
179209
# t1350-config-hooks-path.sh
180210
>actual &&
181-
git hook run test-hook -- ignored 2>>actual &&
182-
git -c core.hooksPath=my-hooks hook run test-hook -- one 2>>actual &&
183-
git -c core.hooksPath=my-hooks/ hook run test-hook -- two 2>>actual &&
184-
git -c core.hooksPath="$PWD/my-hooks" hook run test-hook -- three 2>>actual &&
185-
git -c core.hooksPath="$PWD/my-hooks/" hook run test-hook -- four 2>>actual &&
211+
git hook run --allow-unknown-hook-name test-hook -- ignored 2>>actual &&
212+
git -c core.hooksPath=my-hooks hook run --allow-unknown-hook-name test-hook -- one 2>>actual &&
213+
git -c core.hooksPath=my-hooks/ hook run --allow-unknown-hook-name test-hook -- two 2>>actual &&
214+
git -c core.hooksPath="$PWD/my-hooks" hook run --allow-unknown-hook-name test-hook -- three 2>>actual &&
215+
git -c core.hooksPath="$PWD/my-hooks/" hook run --allow-unknown-hook-name test-hook -- four 2>>actual &&
186216
test_cmp expect actual
187217
'
188218

@@ -262,7 +292,7 @@ test_expect_success 'hook can be configured for multiple events' '
262292
# 'ghi' should be included in both 'pre-commit' and 'test-hook'
263293
git hook list pre-commit >actual &&
264294
grep "ghi" actual &&
265-
git hook list test-hook >actual &&
295+
git hook list --allow-unknown-hook-name test-hook >actual &&
266296
grep "ghi" actual
267297
'
268298

@@ -336,15 +366,15 @@ test_expect_success 'stdin to multiple hooks' '
336366
b3
337367
EOF
338368
339-
git hook run --to-stdin=input test-hook 2>actual &&
369+
git hook run --allow-unknown-hook-name --to-stdin=input test-hook 2>actual &&
340370
test_cmp expected actual
341371
'
342372

343373
test_expect_success 'rejects hooks with no commands configured' '
344374
test_config hook.broken.event "test-hook" &&
345-
test_must_fail git hook list test-hook 2>actual &&
375+
test_must_fail git hook list --allow-unknown-hook-name test-hook 2>actual &&
346376
test_grep "hook.broken.command" actual &&
347-
test_must_fail git hook run test-hook 2>actual &&
377+
test_must_fail git hook run --allow-unknown-hook-name test-hook 2>actual &&
348378
test_grep "hook.broken.command" actual
349379
'
350380

@@ -353,7 +383,7 @@ test_expect_success 'disabled hook is not run' '
353383
test_config hook.skipped.command "echo \"Should not run\"" &&
354384
test_config hook.skipped.enabled false &&
355385
356-
git hook run --ignore-missing test-hook 2>actual &&
386+
git hook run --allow-unknown-hook-name --ignore-missing test-hook 2>actual &&
357387
test_must_be_empty actual
358388
'
359389

@@ -403,7 +433,7 @@ test_expect_success 'globally disabled hook can be re-enabled locally' '
403433
test_config hook.global-hook.enabled true &&
404434
405435
echo "global-hook ran" >expected &&
406-
git hook run test-hook 2>actual &&
436+
git hook run --allow-unknown-hook-name test-hook 2>actual &&
407437
test_cmp expected actual
408438
'
409439

@@ -463,7 +493,7 @@ test_expect_success 'git hook run a hook with a bad shebang' '
463493
464494
test_expect_code 1 git \
465495
-c core.hooksPath=bad-hooks \
466-
hook run test-hook >out 2>err &&
496+
hook run --allow-unknown-hook-name test-hook >out 2>err &&
467497
test_must_be_empty out &&
468498
469499
# TODO: We should emit the same (or at least a more similar)
@@ -487,7 +517,7 @@ test_expect_success 'stdin to hooks' '
487517
EOF
488518
489519
echo hello >input &&
490-
git hook run --to-stdin=input test-hook 2>actual &&
520+
git hook run --allow-unknown-hook-name --to-stdin=input test-hook 2>actual &&
491521
test_cmp expect actual
492522
'
493523

0 commit comments

Comments
 (0)