hooks: allow customizing where the hook directory is

Change the hardcoded lookup for .git/hooks/* to optionally lookup in $(git config core.hooksPath)/* instead. This is essentially a more intrusive version of the git-init ability to specify hooks on init time via init templates. The difference between that facility and this feature is that this can be set up after the fact via e.g. ~/.gitconfig or /etc/gitconfig to apply for all your personal repositories, or all repositories on the system. I plan on using this on a centralized Git server where users can create arbitrary repositories under /gitroot, but I'd like to manage all the hooks that should be run centrally via a unified dispatch mechanism. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Ævar Arnfjörð Bjarmason committed May 4, 2016 at 22:58 UTC 867ad08a2610526edb5723804723d371136fc643
7 files changed +71 -5
Documentation/config.txt
+17
@@ -618,6 +618,23 @@ core.attributesFile::
618 $XDG_CONFIG_HOME/git/attributes. If $XDG_CONFIG_HOME is either not
619 set or empty, $HOME/.config/git/attributes is used instead.
620
621 +core.hooksPath::
622 + By default Git will look for your hooks in the
623 + '$GIT_DIR/hooks' directory. Set this to different path,
624 + e.g. '/etc/git/hooks', and Git will try to find your hooks in
625 + that directory, e.g. '/etc/git/hooks/pre-receive' instead of
626 + in '$GIT_DIR/hooks/pre-receive'.
627 ++
628 +The path can be either absolute or relative. A relative path is
629 +taken as relative to the directory where the hooks are run (see
630 +the "DESCRIPTION" section of linkgit:githooks[5]).
631 ++
632 +This configuration variable is useful in cases where you'd like to
633 +centrally configure your Git hooks instead of configuring them on a
634 +per-repository basis, or as a more flexible and centralized
635 +alternative to having an `init.templateDir` where you've changed
636 +default hooks.
637 +
638 core.editor::
639 Commands such as `commit` and `tag` that lets you edit
640 messages by launching an editor uses the value of this
Documentation/githooks.txt
+8 -4
@@ -7,15 +7,19 @@ githooks - Hooks used by Git
7
8 SYNOPSIS
9 --------
10 -$GIT_DIR/hooks/*
10 +$GIT_DIR/hooks/* (or \`git config core.hooksPath`/*)
11
12
13 DESCRIPTION
14 -----------
15
16 -Hooks are programs you can place in the `$GIT_DIR/hooks` directory to
17 -trigger actions at certain points in git's execution. Hooks that don't
18 -have the executable bit set are ignored.
16 +Hooks are programs you can place in a hooks directory to trigger
17 +actions at certain points in git's execution. Hooks that don't have
18 +the executable bit set are ignored.
19 +
20 +By default the hooks directory is `$GIT_DIR/hooks`, but that can be
21 +changed via the `core.hooksPath` configuration variable (see
22 +linkgit:git-config[1]).
23
24 Before Git invokes a hook, it changes its working directory to either
25 the root of the working tree in a non-bare repository, or to the
cache.h
+1
@@ -655,6 +655,7 @@ extern int shared_repository;
655 extern const char *apply_default_whitespace;
656 extern const char *apply_default_ignorewhitespace;
657 extern const char *git_attributes_file;
658 +extern const char *git_hooks_path;
659 extern int zlib_compression_level;
660 extern int core_compression_level;
661 extern int core_compression_seen;
config.c
+3
@@ -717,6 +717,9 @@ static int git_default_core_config(const char *var, const char *value)
717 if (!strcmp(var, "core.attributesfile"))
718 return git_config_pathname(&git_attributes_file, var, value);
719
720 + if (!strcmp(var, "core.hookspath"))
721 + return git_config_pathname(&git_hooks_path, var, value);
722 +
723 if (!strcmp(var, "core.bare")) {
724 is_bare_repository_cfg = git_config_bool(var, value);
725 return 0;
environment.c
+1
@@ -33,6 +33,7 @@ int shared_repository = PERM_UMASK;
33 const char *apply_default_whitespace;
34 const char *apply_default_ignorewhitespace;
35 const char *git_attributes_file;
36 +const char *git_hooks_path;
37 int zlib_compression_level = Z_BEST_SPEED;
38 int core_compression_level;
39 int core_compression_seen;
run-command.c
+4 -1
@@ -815,7 +815,10 @@ const char *find_hook(const char *name)
815 static struct strbuf path = STRBUF_INIT;
816
817 strbuf_reset(&path);
818 - strbuf_git_path(&path, "hooks/%s", name);
818 + if (git_hooks_path)
819 + strbuf_addf(&path, "%s/%s", git_hooks_path, name);
820 + else
821 + strbuf_git_path(&path, "hooks/%s", name);
822 if (access(path.buf, X_OK) < 0)
823 return NULL;
824 return path.buf;
t/t1350-config-hooks-path.sh new
+37
@@ -0,0 +1,37 @@
1 +#!/bin/sh
2 +
3 +test_description='Test the core.hooksPath configuration variable'
4 +
5 +. ./test-lib.sh
6 +
7 +test_expect_success 'set up a pre-commit hook in core.hooksPath' '
8 + mkdir -p .git/custom-hooks .git/hooks &&
9 + write_script .git/custom-hooks/pre-commit <<-\EOF &&
10 + echo CUSTOM >>actual
11 + EOF
12 + write_script .git/hooks/pre-commit <<-\EOF
13 + echo NORMAL >>actual
14 + EOF
15 +'
16 +
17 +test_expect_success 'Check that various forms of specifying core.hooksPath work' '
18 + test_commit no_custom_hook &&
19 + git config core.hooksPath .git/custom-hooks &&
20 + test_commit have_custom_hook &&
21 + git config core.hooksPath .git/custom-hooks/ &&
22 + test_commit have_custom_hook_trailing_slash &&
23 + git config core.hooksPath "$PWD/.git/custom-hooks" &&
24 + test_commit have_custom_hook_abs_path &&
25 + git config core.hooksPath "$PWD/.git/custom-hooks/" &&
26 + test_commit have_custom_hook_abs_path_trailing_slash &&
27 + cat >expect <<-\EOF &&
28 + NORMAL
29 + CUSTOM
30 + CUSTOM
31 + CUSTOM
32 + CUSTOM
33 + EOF
34 + test_cmp expect actual
35 +'
36 +
37 +test_done