Historically, hooks are declared by placing an executable into $GIT_DIR/hooks/$HOOKNAME (or $HOOKDIR/$HOOKNAME). Although hooks taken from the config are more featureful than hooks placed in the $HOOKDIR, those hooks should not stop working for users who already have them. Let's list them to the user, but instead of displaying a config scope (e.g. "global: blah") we can prefix them with "hookdir:". Signed-off-by: Emily Shaffer <emilyshaffer@xxxxxxxxxx> --- builtin/hook.c | 18 +++++++++++++++--- hook.c | 17 +++++++++++++++++ hook.h | 1 + t/t1360-config-based-hooks.sh | 19 +++++++++++++++++++ 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/builtin/hook.c b/builtin/hook.c index 79e150437e..e82725f0a6 100644 --- a/builtin/hook.c +++ b/builtin/hook.c @@ -39,10 +39,20 @@ static int list(int argc, const char **argv, const char *prefix) list_for_each(pos, head) { struct hook *item = list_entry(pos, struct hook, list); - if (item) - printf("%s: %s\n", - config_scope_name(item->origin), + item = list_entry(pos, struct hook, list); + if (item) { + /* + * TRANSLATORS: "<config scope>: <path>". Both fields + * should be left untranslated; config scope matches the + * output of 'git config --show-scope'. Marked for + * translation to provide better RTL support later. + */ + printf(_("%s: %s\n"), + (item->from_hookdir + ? "hookdir" + : config_scope_name(item->origin)), item->command.buf); + } } clear_hook_list(head); @@ -58,6 +68,8 @@ int cmd_hook(int argc, const char **argv, const char *prefix) if (argc < 2) usage_with_options(builtin_hook_usage, builtin_hook_options); + git_config(git_default_config, NULL); + if (!strcmp(argv[1], "list")) return list(argc - 1, argv + 1, prefix); diff --git a/hook.c b/hook.c index d3e28aa73a..b4994fc108 100644 --- a/hook.c +++ b/hook.c @@ -2,6 +2,7 @@ #include "hook.h" #include "config.h" +#include "run-command.h" void free_hook(struct hook *ptr) { @@ -35,6 +36,7 @@ static void append_or_move_hook(struct list_head *head, const char *command) to_add = xmalloc(sizeof(*to_add)); strbuf_init(&to_add->command, 0); strbuf_addstr(&to_add->command, command); + to_add->from_hookdir = 0; } /* re-set the scope so we show where an override was specified */ @@ -115,6 +117,21 @@ struct list_head* hook_list(const char* hookname) git_config(hook_config_lookup, &cb_data); + if (have_git_dir()) { + const char *legacy_hook_path = find_hook(hookname); + + /* Unconditionally add legacy hook, but annotate it. */ + if (legacy_hook_path) { + struct hook *legacy_hook; + + append_or_move_hook(hook_head, + absolute_path(legacy_hook_path)); + legacy_hook = list_entry(hook_head->prev, struct hook, + list); + legacy_hook->from_hookdir = 1; + } + } + strbuf_release(&hook_key); return hook_head; } diff --git a/hook.h b/hook.h index 042cab8446..b6c5480325 100644 --- a/hook.h +++ b/hook.h @@ -11,6 +11,7 @@ struct hook { enum config_scope origin; /* The literal command to run. */ struct strbuf command; + unsigned from_hookdir : 1; }; /* diff --git a/t/t1360-config-based-hooks.sh b/t/t1360-config-based-hooks.sh index 6e4a3e763f..0f12af4659 100755 --- a/t/t1360-config-based-hooks.sh +++ b/t/t1360-config-based-hooks.sh @@ -23,6 +23,14 @@ setup_hookcmd () { test_config_global hookcmd.abc.command "/path/abc" --add } +setup_hookdir () { + mkdir .git/hooks + write_script .git/hooks/pre-commit <<-EOF + echo \"Legacy Hook\" + EOF + test_when_finished rm -rf .git/hooks +} + test_expect_success 'git hook rejects commands without a mode' ' test_must_fail git hook pre-commit ' @@ -85,4 +93,15 @@ test_expect_success 'git hook list reorders on duplicate commands' ' test_cmp expected actual ' +test_expect_success 'git hook list shows hooks from the hookdir' ' + setup_hookdir && + + cat >expected <<-EOF && + hookdir: $(pwd)/.git/hooks/pre-commit + EOF + + git hook list pre-commit >actual && + test_cmp expected actual +' + test_done -- 2.31.1.818.g46aad6cb9e-goog