LoginSignup
51
29

More than 3 years have passed since last update.

globalなgit-hooksを設定して、すべてのリポジトリで共有のhooksを使う

Posted at

皆さん、git-hooksは使っているでしょうか。

${REPOSITORY_ROOT}/.git/hooks/ 以下に決められた名前でスクリプトを作成すると、対応した操作に合わせて、その名前のスクリプトが自動で実行されます。

例えば、${REPOSITORY_ROOT}/.git/hooks/pre-commit ならコミット前にそのスクリプトを実行して、スクリプトの終了コードが0でないとき、commit前に処理が停止します。

よく使われるのは、pre-commitを使用して、コミット直前にCIと同様の構文チェックや、rubocop等のテストをすることではないでしょうか。

基本的には、上記のようにRepositoryごとにhooksは書くのがデフォルトですが、
最近、私はすべてのRepositoryで共通するような、設定や除外ファイルの記載をしたgitconfigやgitignoreをその他設定系ファイルとまとめてgit管理しています。

そんな中、git-hooksもすべてのリポジトリで共通化できないかと思い、調べてみました。

(この記事は基本的にMac環境での動作を前提に書いています。ほとんどの処理は他環境でも動くと思いますが、一部Homebrewに依存した処理があるので、その部分は読み替えてください。)

先行事例の調査

「git hook global」でGoogle検索するとそれっぽい情報がいくつか出てきます。

しかしこれらを読んでいくと、あくまでinitやclone時のテンプレートに含めて、最初にテンプレートからコピー=実質的にすべてのリポジトリで共通化という感じでした。

この方法だと以下のような欠点が考えられます。

  • 一度init, cloneしたリポジトリに一律で新しい変更を反映できない
  • すでにinit, clone済みのリポジトリには自動で反映されない

ただ記事自体の最終更新日も古く、gitは2系になっていろいろ新しい機能・設定も追加されています。
そのため、 gitconfigをグローバルで設定するときに使われる ~/.gitconfig のようなやり方があるのではないかと、公式ドキュメントを確認しました。

ありました。

Git - githooks Documentation

このドキュメントには、以下のように記載されています。

By default the hooks directory is $GIT_DIR/hooks, but that can be changed via the core.hooksPath configuration variable (see git-config[1]).

すなわち、 core.hooksPath にhooksディレクトリのパスを指定すれば、そちらを優先的に読み込んでくれるようです。

なので、私は管理しやすいように core.hooksPath を .config/git/hooks に指定しました。
(最近のgitでは、 .config/git/ にグローバルなデフォルト設定を書き込むことが多いのでそれに倣った形です)

core.hooksPath に書き込むには以下のコマンドを実行する、あるいは ~/.gitconfig or ~/.config/git/config に直接書き込む方法があります。

git config --global core.hooksPath .config/git/hooks

設定ファイルに直接書き込む場合は、以下の設定をconfigファイルに追記します。

~/.gitconfig
 [core]
    hooksPath = ~/.config/git/hooks

これで、pre-commitなどのgit-hooks時に ${GIT_ROOT}/.git/hooks ではなく、設定したディレクトリのファイルを読み込んでくれるようになりました。

gitリポジトリ内のgit-hooksも実行するようにする

上記の方法ですべてのgitリポジトリにおいて、git-hooksでは .config/git/hooks のスクリプトを実行するようになりました。

しかし、もしもリポジトリごとに個別のgit-hooksを設定したくなったとき、このままでは ${GIT_ROOT}/.git/hooks にファイルを置いても実行されません。

そのため、リポジトリ側においたスクリプトも実行されるように .config/git/hooks においたスクリプト側で設定します。

この記事のスクリプトこの記事のgitリポジトリのルートを取得するコマンドを参考に、以下のようなスクリプトを作成しました。

_local-hook-exec
#!/bin/bash

GIT_ROOT=`git rev-parse --show-superproject-working-tree --show-toplevel | head -1`
HOOK_NAME=`basename $0`
LOCAL_HOOK="${GIT_ROOT}/.git/hooks/${HOOK_NAME}"

if [ -e $LOCAL_HOOK ]; then
  source $LOCAL_HOOK
fi

このスクリプトでは、そのリポジトリ内に同名のgit-hooks用のスクリプトが存在している場合、そのスクリプトを実行するようになっています。

このファイルをすべてのgit-hooksスクリプトで実行されるようにします。
すべてのgit-hooksスクリプトを .config/git/hooks に一括で作成するため、gitに用意されているsampleファイルからスクリプト名を取得して作成するコマンドを用意しました。

ここでは↑に書いたスクリプトを _local-hook-exec というファイル名で作成済と仮定します。
(_ を先頭につけているのは、git-hooks用のコマンドか否かをわかりやすくするためです)

また、sampleのPATHを取得するため、6行目でbrewのコマンドでgitがインストールされた場所を取得しています。
Mac以外、あるいはHomebrew以外でインストールした場合は、適切に読み替えてください。

GIT_HOOKS_DIR="${HOME}/.config/git/hooks"

echo '#!/bin/bash' >> hooks_template
echo '' >> hooks_template
echo 'source `dirname ${0}`/_local-hook-exec' >> hooks_template
ls `brew --prefix git`/share/git-core/templates/hooks | sed s/.sample//g | xargs -I{} cp -n hooks_template ${GIT_HOOKS_DIR}/{}
chmod 700 ${GIT_HOOKS_DIR}/*
rm hooks_template

このコマンドでは git側にあるhooksのテンプレートからhooksのファイル名一覧を取得→取得したファイルに対して、以下のスクリプトをコピーしています。

#!/bin/bash

source `dirname ${0}`/_local-hook-exec

これにより、すべてのコマンド実行時に _local-hook-exec が呼ばれる=gitリポジトリ側に設置したgit-hooksスクリプトが実行されるようになりました。

また、今後git側で対応するhooksが増えても同様のスクリプトを実行するだけで、同様の状態にすることができます。

まとめ

この手順を踏むことで、すべてのRepository共通のhooks、および各Repository専用のhooksをそれぞれ実行できる様になりました。

これで、言語共通で同じ処理をしたいときは、ファイルの拡張子ごとにその処理をするように、プロジェクト特有の処理はそのRepository内で定義して実行できるようになりました。

例えば、すべてのRepositoryで共通してやりたいこととしては、AWSアクセスキーをgitに誤って登録しないようにすることなど、セキュリティ的に含まないようにする処理などいろいろ考えられます。

私自身もどこまでglobalにするかは考えている最中ですが、私のglobalなgit-hooksはここで公開していますので、興味がある方は参考にしてください。

51
29
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
51
29