Skip to content

Commit 0cd4fb9

Browse files
committed
Merge branch 'ar/config-hook-cleanups'
Code clean-up around the recent "hooks defined in config" topic. * ar/config-hook-cleanups: hook: reject unknown hook names in git-hook(1) hook: show disabled hooks in "git hook list" hook: show config scope in git hook list hook: introduce hook_config_cache_entry for per-hook data t1800: add test to verify hook execution ordering hook: make consistent use of friendly-name in docs hook: replace hook_list_clear() -> string_list_clear_func() hook: detect & emit two more bugs hook: rename cb_data_free/alloc -> hook_data_free/alloc hook: fix minor style issues builtin/receive-pack: properly init receive_hook strbuf hook: move unsorted_string_list_remove() to string-list.[ch]
2 parents 4e58217 + 5c58dbc commit 0cd4fb9

12 files changed

Lines changed: 424 additions & 175 deletions

File tree

Documentation/config/hook.adoc

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1-
hook.<name>.command::
2-
The command to execute for `hook.<name>`. `<name>` is a unique
3-
"friendly" name that identifies this hook. (The hook events that
4-
trigger the command are configured with `hook.<name>.event`.) The
5-
value can be an executable path or a shell oneliner. If more than
6-
one value is specified for the same `<name>`, only the last value
7-
parsed is used. See linkgit:git-hook[1].
1+
hook.<friendly-name>.command::
2+
The command to execute for `hook.<friendly-name>`. `<friendly-name>`
3+
is a unique name that identifies this hook. The hook events that
4+
trigger the command are configured with `hook.<friendly-name>.event`.
5+
The value can be an executable path or a shell oneliner. If more than
6+
one value is specified for the same `<friendly-name>`, only the last
7+
value parsed is used. See linkgit:git-hook[1].
88

9-
hook.<name>.event::
10-
The hook events that trigger `hook.<name>`. The value is the name
11-
of a hook event, like "pre-commit" or "update". (See
9+
hook.<friendly-name>.event::
10+
The hook events that trigger `hook.<friendly-name>`. The value is the
11+
name of a hook event, like "pre-commit" or "update". (See
1212
linkgit:githooks[5] for a complete list of hook events.) On the
13-
specified event, the associated `hook.<name>.command` is executed.
14-
This is a multi-valued key. To run `hook.<name>` on multiple
13+
specified event, the associated `hook.<friendly-name>.command` is executed.
14+
This is a multi-valued key. To run `hook.<friendly-name>` on multiple
1515
events, specify the key more than once. An empty value resets
1616
the list of events, clearing any previously defined events for
17-
`hook.<name>`. See linkgit:git-hook[1].
17+
`hook.<friendly-name>`. See linkgit:git-hook[1].
1818

19-
hook.<name>.enabled::
20-
Whether the hook `hook.<name>` is enabled. Defaults to `true`.
19+
hook.<friendly-name>.enabled::
20+
Whether the hook `hook.<friendly-name>` is enabled. Defaults to `true`.
2121
Set to `false` to disable the hook without removing its
2222
configuration. This is particularly useful when a hook is defined
2323
in a system or global config file and needs to be disabled for a

Documentation/git-hook.adoc

Lines changed: 20 additions & 7 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] <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
-----------
@@ -44,7 +44,7 @@ event`), and then `~/bin/spellchecker` will have a chance to check your commit
4444
message (during the `commit-msg` hook event).
4545

4646
Commands are run in the order Git encounters their associated
47-
`hook.<name>.event` configs during the configuration parse (see
47+
`hook.<friendly-name>.event` configs during the configuration parse (see
4848
linkgit:git-config[1]). Although multiple `hook.linter.event` configs can be
4949
added, only one `hook.linter.command` event is valid - Git uses "last-one-wins"
5050
to determine which command to run.
@@ -76,10 +76,10 @@ first start `~/bin/linter --cpp20` and second start `~/bin/leak-detector`. It
7676
would evaluate the output of each when deciding whether to proceed with the
7777
commit.
7878

79-
For a full list of hook events which you can set your `hook.<name>.event` to,
79+
For a full list of hook events which you can set your `hook.<friendly-name>.event` to,
8080
and how hooks are invoked during those events, see linkgit:githooks[5].
8181

82-
Git will ignore any `hook.<name>.event` that specifies an event it doesn't
82+
Git will ignore any `hook.<friendly-name>.event` that specifies an event it doesn't
8383
recognize. This is intended so that tools which wrap Git can use the hook
8484
infrastructure to run their own hooks; see "WRAPPERS" for more guidance.
8585

@@ -113,14 +113,21 @@ Any positional arguments to the hook should be passed after a
113113
mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See
114114
linkgit:githooks[5] for arguments hooks might expect (if any).
115115
116-
list [-z]::
116+
list [-z] [--show-scope]::
117117
Print a list of hooks which will be run on `<hook-name>` event. If no
118118
hooks are configured for that event, print a warning and return 1.
119119
Use `-z` to terminate output lines with NUL instead of newlines.
120120
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
@@ -134,6 +141,12 @@ OPTIONS
134141
-z::
135142
Terminate "list" output lines with NUL instead of newlines.
136143

144+
--show-scope::
145+
For "list"; prefix each configured hook's friendly name with a
146+
tab-separated config scope (e.g. `local`, `global`, `system`),
147+
mirroring the output style of `git config --show-scope`. Traditional
148+
hooks from the hookdir are unaffected.
149+
137150
WRAPPERS
138151
--------
139152
@@ -153,7 +166,7 @@ Then, in your 'mywrapper' tool, you can invoke any users' configured hooks by
153166
running:
154167
155168
----
156-
git hook run mywrapper-start-tests \
169+
git hook run --allow-unknown-hook-name mywrapper-start-tests \
157170
# providing something to stdin
158171
--stdin some-tempfile-123 \
159172
# execute hooks in serial

Makefile

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

26762676
help.sp help.s help.o: command-list.h
26772677
builtin/bugreport.sp builtin/bugreport.s builtin/bugreport.o: hook-list.h
2678+
builtin/hook.sp builtin/hook.s builtin/hook.o: hook-list.h
26782679

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

builtin/hook.c

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,22 @@
44
#include "environment.h"
55
#include "gettext.h"
66
#include "hook.h"
7+
#include "hook-list.h"
78
#include "parse-options.h"
8-
#include "strvec.h"
9-
#include "abspath.h"
109

1110
#define BUILTIN_HOOK_RUN_USAGE \
12-
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>]")
1312
#define BUILTIN_HOOK_LIST_USAGE \
14-
N_("git hook list [-z] <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+
}
1523

1624
static const char * const builtin_hook_usage[] = {
1725
BUILTIN_HOOK_RUN_USAGE,
@@ -35,11 +43,17 @@ static int list(int argc, const char **argv, const char *prefix,
3543
struct string_list_item *item;
3644
const char *hookname = NULL;
3745
int line_terminator = '\n';
46+
int show_scope = 0;
47+
int allow_unknown = 0;
3848
int ret = 0;
3949

4050
struct option list_options[] = {
4151
OPT_SET_INT('z', NULL, &line_terminator,
4252
N_("use NUL as line terminator"), '\0'),
53+
OPT_BOOL(0, "show-scope", &show_scope,
54+
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")),
4357
OPT_END(),
4458
};
4559

@@ -51,15 +65,22 @@ static int list(int argc, const char **argv, const char *prefix,
5165
* arguments later they probably should be caught by parse_options.
5266
*/
5367
if (argc != 1)
54-
usage_msg_opt(_("You must specify a hook event name to list."),
68+
usage_msg_opt(_("you must specify a hook event name to list"),
5569
builtin_hook_list_usage, list_options);
5670

5771
hookname = argv[0];
5872

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+
5980
head = list_hooks(repo, hookname, NULL);
6081

6182
if (!head->nr) {
62-
warning(_("No hooks found for event '%s'"), hookname);
83+
warning(_("no hooks found for event '%s'"), hookname);
6384
ret = 1; /* no hooks found */
6485
goto cleanup;
6586
}
@@ -71,16 +92,27 @@ static int list(int argc, const char **argv, const char *prefix,
7192
case HOOK_TRADITIONAL:
7293
printf("%s%c", _("hook from hookdir"), line_terminator);
7394
break;
74-
case HOOK_CONFIGURED:
75-
printf("%s%c", h->u.configured.friendly_name, line_terminator);
95+
case HOOK_CONFIGURED: {
96+
const char *name = h->u.configured.friendly_name;
97+
const char *scope = show_scope ?
98+
config_scope_name(h->u.configured.scope) : NULL;
99+
if (scope)
100+
printf("%s\t%s%s%c", scope,
101+
h->u.configured.disabled ? "disabled\t" : "",
102+
name, line_terminator);
103+
else
104+
printf("%s%s%c",
105+
h->u.configured.disabled ? "disabled\t" : "",
106+
name, line_terminator);
76107
break;
108+
}
77109
default:
78110
BUG("unknown hook kind");
79111
}
80112
}
81113

82114
cleanup:
83-
hook_list_clear(head, NULL);
115+
string_list_clear_func(head, hook_free);
84116
free(head);
85117
return ret;
86118
}
@@ -91,8 +123,11 @@ static int run(int argc, const char **argv, const char *prefix,
91123
int i;
92124
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
93125
int ignore_missing = 0;
126+
int allow_unknown = 0;
94127
const char *hook_name;
95128
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")),
96131
OPT_BOOL(0, "ignore-missing", &ignore_missing,
97132
N_("silently ignore missing requested <hook-name>")),
98133
OPT_STRING(0, "to-stdin", &opt.path_to_stdin, N_("path"),
@@ -124,6 +159,14 @@ static int run(int argc, const char **argv, const char *prefix,
124159
repo_config(the_repository, git_default_config, NULL);
125160

126161
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+
127170
if (!ignore_missing)
128171
opt.error_if_missing = 1;
129172
ret = run_hooks_opt(the_repository, hook_name, &opt);

builtin/receive-pack.c

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,45 @@
33

44
#include "builtin.h"
55
#include "abspath.h"
6-
6+
#include "commit.h"
7+
#include "commit-reach.h"
78
#include "config.h"
9+
#include "connect.h"
10+
#include "connected.h"
811
#include "environment.h"
12+
#include "exec-cmd.h"
13+
#include "fsck.h"
914
#include "gettext.h"
15+
#include "gpg-interface.h"
1016
#include "hex.h"
11-
#include "lockfile.h"
12-
#include "pack.h"
13-
#include "refs.h"
14-
#include "pkt-line.h"
15-
#include "sideband.h"
16-
#include "run-command.h"
1717
#include "hook.h"
18-
#include "exec-cmd.h"
19-
#include "commit.h"
18+
#include "lockfile.h"
2019
#include "object.h"
21-
#include "remote.h"
22-
#include "connect.h"
23-
#include "string-list.h"
24-
#include "oid-array.h"
25-
#include "connected.h"
26-
#include "strvec.h"
27-
#include "version.h"
28-
#include "gpg-interface.h"
29-
#include "sigchain.h"
30-
#include "fsck.h"
31-
#include "tmp-objdir.h"
32-
#include "oidset.h"
33-
#include "packfile.h"
3420
#include "object-file.h"
3521
#include "object-name.h"
3622
#include "odb.h"
23+
#include "oid-array.h"
24+
#include "oidset.h"
25+
#include "pack.h"
26+
#include "packfile.h"
27+
#include "parse-options.h"
28+
#include "pkt-line.h"
3729
#include "protocol.h"
38-
#include "commit-reach.h"
30+
#include "refs.h"
31+
#include "remote.h"
32+
#include "run-command.h"
3933
#include "server-info.h"
34+
#include "setup.h"
35+
#include "shallow.h"
36+
#include "sideband.h"
37+
#include "sigchain.h"
38+
#include "string-list.h"
39+
#include "strvec.h"
40+
#include "tmp-objdir.h"
4041
#include "trace.h"
4142
#include "trace2.h"
43+
#include "version.h"
4244
#include "worktree.h"
43-
#include "shallow.h"
44-
#include "setup.h"
45-
#include "parse-options.h"
4645

4746
static const char * const receive_pack_usage[] = {
4847
N_("git receive-pack <git-dir>"),
@@ -904,11 +903,14 @@ static int feed_receive_hook_cb(int hook_stdin_fd, void *pp_cb UNUSED, void *pp_
904903
static void *receive_hook_feed_state_alloc(void *feed_pipe_ctx)
905904
{
906905
struct receive_hook_feed_state *init_state = feed_pipe_ctx;
907-
struct receive_hook_feed_state *data = xcalloc(1, sizeof(*data));
906+
struct receive_hook_feed_state *data;
907+
908+
CALLOC_ARRAY(data, 1);
908909
data->report = init_state->report;
909910
data->cmd = init_state->cmd;
910911
data->skip_broken = init_state->skip_broken;
911912
strbuf_init(&data->buf, 0);
913+
912914
return data;
913915
}
914916

@@ -928,7 +930,11 @@ static int run_receive_hook(struct command *commands,
928930
{
929931
struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
930932
struct command *iter = commands;
931-
struct receive_hook_feed_state feed_init_state = { 0 };
933+
struct receive_hook_feed_state feed_init_state = {
934+
.cmd = commands,
935+
.skip_broken = skip_broken,
936+
.buf = STRBUF_INIT,
937+
};
932938
struct async sideband_async;
933939
int sideband_async_started = 0;
934940
int saved_stderr = -1;
@@ -961,8 +967,6 @@ static int run_receive_hook(struct command *commands,
961967
prepare_sideband_async(&sideband_async, &saved_stderr, &sideband_async_started);
962968

963969
/* set up stdin callback */
964-
feed_init_state.cmd = commands;
965-
feed_init_state.skip_broken = skip_broken;
966970
opt.feed_pipe_ctx = &feed_init_state;
967971
opt.feed_pipe = feed_receive_hook_cb;
968972
opt.feed_pipe_cb_data_alloc = receive_hook_feed_state_alloc;

0 commit comments

Comments
 (0)