Skip to content

Commit c45caa8

Browse files
nasamuffingitster
authored andcommitted
hook: add 'run' subcommand
In order to enable hooks to be run as an external process, by a standalone Git command, or by tools which wrap Git, provide an external means to run all configured hook commands for a given hook event. For now, the hook commands will in config order, in series. As alternate ordering or parallelism is supported in the future, we should add knobs to use those to the command line as well. As with the legacy hook implementation, all stdout generated by hook commands is redirected to stderr. Piping from stdin is not yet supported. Legacy hooks (those present in $GITDIR/hooks) are run at the end of the execution list. For now, there is no way to disable them. Users may wish to provide hook commands like 'git config hook.pre-commit.command "~/linter.sh --pre-commit"'. To enable this, the contents of the 'hook.*.command' and 'hookcmd.*.command' strings are first split by space or quotes into an argv_array, then expanded with 'expand_user_path()'. Signed-off-by: Emily Shaffer <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent c1debc6 commit c45caa8

File tree

4 files changed

+103
-0
lines changed

4 files changed

+103
-0
lines changed

builtin/hook.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
#include "hook.h"
66
#include "parse-options.h"
77
#include "strbuf.h"
8+
#include "argv-array.h"
89

910
static const char * const builtin_hook_usage[] = {
1011
N_("git hook list <hookname>"),
12+
N_("git hook run [(-e|--env)=<var>...] [(-a|--arg)=<arg>...] <hookname>"),
1113
NULL
1214
};
1315

@@ -62,6 +64,32 @@ static int list(int argc, const char **argv, const char *prefix)
6264
return 0;
6365
}
6466

67+
static int run(int argc, const char **argv, const char *prefix)
68+
{
69+
struct strbuf hookname = STRBUF_INIT;
70+
struct argv_array env_argv = ARGV_ARRAY_INIT;
71+
struct argv_array arg_argv = ARGV_ARRAY_INIT;
72+
73+
struct option run_options[] = {
74+
OPT_ARGV_ARRAY('e', "env", &env_argv, N_("var"),
75+
N_("environment variables for hook to use")),
76+
OPT_ARGV_ARRAY('a', "arg", &arg_argv, N_("args"),
77+
N_("argument to pass to hook")),
78+
OPT_END(),
79+
};
80+
81+
argc = parse_options(argc, argv, prefix, run_options,
82+
builtin_hook_usage, 0);
83+
84+
if (argc < 1)
85+
usage_msg_opt(_("a hookname must be provided to operate on."),
86+
builtin_hook_usage, run_options);
87+
88+
strbuf_addstr(&hookname, argv[0]);
89+
90+
return run_hooks(env_argv.argv, &hookname, &arg_argv);
91+
}
92+
6593
int cmd_hook(int argc, const char **argv, const char *prefix)
6694
{
6795
struct option builtin_hook_options[] = {
@@ -72,6 +100,8 @@ int cmd_hook(int argc, const char **argv, const char *prefix)
72100

73101
if (!strcmp(argv[1], "list"))
74102
return list(argc - 1, argv + 1, prefix);
103+
if (!strcmp(argv[1], "run"))
104+
return run(argc - 1, argv + 1, prefix);
75105

76106
usage_with_options(builtin_hook_usage, builtin_hook_options);
77107
}

hook.c

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "hook.h"
44
#include "config.h"
5+
#include "run-command.h"
56

67
static LIST_HEAD(hook_head);
78

@@ -78,6 +79,7 @@ static int hook_config_lookup(const char *key, const char *value, void *hook_key
7879
struct list_head* hook_list(const struct strbuf* hookname)
7980
{
8081
struct strbuf hook_key = STRBUF_INIT;
82+
const char *legacy_hook_path = NULL;
8183

8284
if (!hookname)
8385
return NULL;
@@ -86,5 +88,45 @@ struct list_head* hook_list(const struct strbuf* hookname)
8688

8789
git_config(hook_config_lookup, (void*)hook_key.buf);
8890

91+
legacy_hook_path = find_hook(hookname->buf);
92+
93+
/* TODO: check hook.runHookDir */
94+
if (legacy_hook_path)
95+
emplace_hook(&hook_head, legacy_hook_path);
96+
8997
return &hook_head;
9098
}
99+
100+
int run_hooks(const char *const *env, const struct strbuf *hookname,
101+
const struct argv_array *args)
102+
{
103+
struct list_head *to_run, *pos = NULL, *tmp = NULL;
104+
int rc = 0;
105+
106+
to_run = hook_list(hookname);
107+
108+
list_for_each_safe(pos, tmp, to_run) {
109+
struct child_process hook_proc = CHILD_PROCESS_INIT;
110+
struct hook *hook = list_entry(pos, struct hook, list);
111+
112+
/* add command */
113+
argv_array_push(&hook_proc.args, hook->command.buf);
114+
115+
/*
116+
* add passed-in argv, without expanding - let the user get back
117+
* exactly what they put in
118+
*/
119+
if (args)
120+
argv_array_pushv(&hook_proc.args, args->argv);
121+
122+
hook_proc.env = env;
123+
hook_proc.no_stdin = 1;
124+
hook_proc.stdout_to_stderr = 1;
125+
hook_proc.trace2_hook_name = hook->command.buf;
126+
hook_proc.use_shell = 1;
127+
128+
rc |= run_command(&hook_proc);
129+
}
130+
131+
return rc;
132+
}

hook.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "config.h"
22
#include "list.h"
33
#include "strbuf.h"
4+
#include "argv-array.h"
45

56
struct hook
67
{
@@ -10,6 +11,8 @@ struct hook
1011
};
1112

1213
struct list_head* hook_list(const struct strbuf *hookname);
14+
int run_hooks(const char *const *env, const struct strbuf *hookname,
15+
const struct argv_array *args);
1316

1417
void free_hook(struct hook *ptr);
1518
void clear_hook_list(void);

t/t1360-config-based-hooks.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,32 @@ test_expect_success 'git hook list --porcelain prints just the command' '
8484
test_cmp expected actual
8585
'
8686

87+
test_expect_success 'inline hook definitions execute oneliners' '
88+
test_config hook.pre-commit.command "echo \"Hello World\"" &&
89+
90+
echo "Hello World" >expected &&
91+
92+
# hooks are run with stdout_to_stderr = 1
93+
git hook run pre-commit 2>actual &&
94+
test_cmp expected actual
95+
'
96+
97+
test_expect_success 'inline hook definitions resolve paths' '
98+
cat >~/sample-hook.sh <<-EOF &&
99+
echo \"Sample Hook\"
100+
EOF
101+
102+
test_when_finished "rm ~/sample-hook.sh" &&
103+
104+
chmod +x ~/sample-hook.sh &&
105+
106+
test_config hook.pre-commit.command "~/sample-hook.sh" &&
107+
108+
echo \"Sample Hook\" >expected &&
109+
110+
# hooks are run with stdout_to_stderr = 1
111+
git hook run pre-commit 2>actual &&
112+
test_cmp expected actual
113+
'
114+
87115
test_done

0 commit comments

Comments
 (0)