@@ -7,3 +7,8 @@ hookcmd.<name>.command::
A command to execute during a hook for which <name> has been specified
as a command. This can be an executable on your device or a oneliner for
your shell. See linkgit:git-hook[1].
+
+hook.runHookDir::
+ Controls how hooks contained in your hookdir are executed. Can be any of
+ "yes", "warn", "interactive", or "no". Defaults to "yes". See
+ linkgit:git-hook[1] and linkgit:git-config[1] "core.hooksPath").
@@ -11,6 +11,8 @@ static const char * const builtin_hook_usage[] = {
NULL
};
+static enum hookdir_opt should_run_hookdir;
+
static int list(int argc, const char **argv, const char *prefix)
{
struct list_head *head, *pos;
@@ -41,6 +43,26 @@ static int list(int argc, const char **argv, const char *prefix)
return 0;
}
+ switch (should_run_hookdir) {
+ case hookdir_no:
+ strbuf_addstr(&hookdir_annotation, _(" (will not run)"));
+ break;
+ case hookdir_interactive:
+ strbuf_addstr(&hookdir_annotation, _(" (will prompt)"));
+ break;
+ case hookdir_warn:
+ case hookdir_unknown:
+ strbuf_addstr(&hookdir_annotation, _(" (will warn)"));
+ break;
+ case hookdir_yes:
+ /*
+ * The default behavior should agree with
+ * hook.c:configured_hookdir_opt().
+ */
+ default:
+ break;
+ }
+
list_for_each(pos, head) {
item = list_entry(pos, struct hook, list);
if (item) {
@@ -64,14 +86,40 @@ static int list(int argc, const char **argv, const char *prefix)
int cmd_hook(int argc, const char **argv, const char *prefix)
{
+ const char *run_hookdir = NULL;
+
struct option builtin_hook_options[] = {
+ OPT_STRING(0, "run-hookdir", &run_hookdir, N_("option"),
+ N_("what to do with hooks found in the hookdir")),
OPT_END(),
};
- if (argc < 2)
+
+ argc = parse_options(argc, argv, prefix, builtin_hook_options,
+ builtin_hook_usage, 0);
+
+ /* after the parse, we should have "<command> <hookname> <args...>" */
+ if (argc < 1)
usage_with_options(builtin_hook_usage, builtin_hook_options);
- if (!strcmp(argv[1], "list"))
- return list(argc - 1, argv + 1, prefix);
+
+ /* argument > config */
+ if (run_hookdir)
+ if (!strcmp(run_hookdir, "no"))
+ should_run_hookdir = hookdir_no;
+ else if (!strcmp(run_hookdir, "yes"))
+ should_run_hookdir = hookdir_yes;
+ else if (!strcmp(run_hookdir, "warn"))
+ should_run_hookdir = hookdir_warn;
+ else if (!strcmp(run_hookdir, "interactive"))
+ should_run_hookdir = hookdir_interactive;
+ else
+ die(_("'%s' is not a valid option for --run-hookdir "
+ "(yes, warn, interactive, no)"), run_hookdir);
+ else
+ should_run_hookdir = configured_hookdir_opt();
+
+ if (!strcmp(argv[0], "list"))
+ return list(argc, argv, prefix);
usage_with_options(builtin_hook_usage, builtin_hook_options);
}
@@ -97,6 +97,27 @@ static int hook_config_lookup(const char *key, const char *value, void *cb_data)
return 0;
}
+enum hookdir_opt configured_hookdir_opt(void)
+{
+ const char *key;
+ if (git_config_get_value("hook.runhookdir", &key))
+ return hookdir_yes; /* by default, just run it. */
+
+ if (!strcmp(key, "no"))
+ return hookdir_no;
+
+ if (!strcmp(key, "yes"))
+ return hookdir_yes;
+
+ if (!strcmp(key, "warn"))
+ return hookdir_warn;
+
+ if (!strcmp(key, "interactive"))
+ return hookdir_interactive;
+
+ return hookdir_unknown;
+}
+
struct list_head* hook_list(const struct strbuf* hookname)
{
struct strbuf hook_key = STRBUF_INIT;
@@ -21,6 +21,21 @@ struct hook
*/
struct list_head* hook_list(const struct strbuf *hookname);
+enum hookdir_opt
+{
+ hookdir_no,
+ hookdir_warn,
+ hookdir_interactive,
+ hookdir_yes,
+ hookdir_unknown,
+};
+
+/*
+ * Provides the hookdir_opt specified in the config without consulting any
+ * command line arguments.
+ */
+enum hookdir_opt configured_hookdir_opt(void);
+
/* Free memory associated with a 'struct hook' */
void free_hook(struct hook *ptr);
/* Empties the list at 'head', calling 'free_hook()' on each entry */
@@ -104,4 +104,47 @@ test_expect_success 'git hook list shows hooks from the hookdir' '
test_cmp expected actual
'
+test_expect_success 'hook.runHookDir = no is respected by list' '
+ setup_hookdir &&
+
+ test_config hook.runHookDir "no" &&
+
+ cat >expected <<-EOF &&
+ hookdir: $(pwd)/.git/hooks/pre-commit (will not run)
+ EOF
+
+ git hook list pre-commit >actual &&
+ # the hookdir annotation is translated
+ test_i18ncmp expected actual
+'
+
+test_expect_success 'hook.runHookDir = warn is respected by list' '
+ setup_hookdir &&
+
+ test_config hook.runHookDir "warn" &&
+
+ cat >expected <<-EOF &&
+ hookdir: $(pwd)/.git/hooks/pre-commit (will warn)
+ EOF
+
+ git hook list pre-commit >actual &&
+ # the hookdir annotation is translated
+ test_i18ncmp expected actual
+'
+
+
+test_expect_success 'hook.runHookDir = interactive is respected by list' '
+ setup_hookdir &&
+
+ test_config hook.runHookDir "interactive" &&
+
+ cat >expected <<-EOF &&
+ hookdir: $(pwd)/.git/hooks/pre-commit (will prompt)
+ EOF
+
+ git hook list pre-commit >actual &&
+ # the hookdir annotation is translated
+ test_i18ncmp expected actual
+'
+
test_done
Include hooks specified in the hook directory in the list of hooks to run. These hooks do need to be treated differently from config-specified ones - they do not need to run in a shell, and later on may be disabled or warned about based on a config setting. Because they are at least as local as the local config, we'll run them last - to keep the hook execution order from global to local. Signed-off-by: Emily Shaffer <emilyshaffer@google.com> --- Notes: Newly split into its own commit since v4, and taking place much sooner. An unfortunate side effect of adding this support *before* the hook.runHookDir support is that the labels on the list are not clear - because we aren't yet flagging which hooks are from the hookdir versus the config. I suppose we could move the addition of that field to the struct hook up to this patch, but it didn't make a lot of sense to me to do it just for cosmetic purposes. Documentation/config/hook.txt | 5 ++++ builtin/hook.c | 54 +++++++++++++++++++++++++++++++++-- hook.c | 21 ++++++++++++++ hook.h | 15 ++++++++++ t/t1360-config-based-hooks.sh | 43 ++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 3 deletions(-)