Gitには、Gitフックという便利な機能があります。git commit
やgit push
の実行をフックして、なんらかの処理を差し込めるようにするものです。コミット前にLintやFormatterをかけるような使い方をよく見かけます。
そんな便利なGitフックですが、これは完全に.git
ごと(以後「ローカル」と呼びます)にしか設定できません。リポジトリに対して設定して、そのリポジトリにコミットするごとに同じLintをかけるようなことはできません1。ユーザごとに好きなフックを、すべてのリポジトリに対して実行する、ようなこともできません。
それをなんとかしよう、というのが今回の話です。
元々、init.templateDir
という設定があり、これを使うとリポジトリの初期化時に自動でコピーされるフックを好きなものに置き換えることはできたようです。ただ初期化時にコピーするだけなので、その後追加したフックはぜんぶ手動でなんとかするしかないし、変更したフックは反映されないし、とグローバルに設定したいという要求を満たすようなものではありませんでした。
しばらくそんな状況であきらめていたのですが、先日調べ直したところ、いつの間にか2core.hooksPath
という、フックを探しに行くディレクトリを指定する設定ができていることを以下のページで知りました。
globalなgit-hooksを設定して、すべてのリポジトリで共有のhooksを使う #Git - Qiita
# ...
[core]
hooksPath = ~/.config/git/hooks
# ...
これを使えば解決、と思ったのですが、この設定は設定したパスを見に行くようになるだけなので、グローバルなフックを設定した場合、今度はローカルなフックを設定することができません。あるリポジトリでのみ使いたいフックもあるので、これでは不便です。
上記ページには、単にグローバルなフックからローカルなフックを実行するようにして対応する方法も書かれていたので、そちらを参考にローカルなフックも動くようにします。
# pre-commit等
# ...
local_git_hook_path="$(git rev-parse --git-dir)"/hooks/"$(basename "$0")"
if [ -x "$local_git_hook_path" ]; then
"$local_git_hook_path" "$@"
fi
# ...
これで解決! と思いきや、今度はHuskyを競合してしましました。Huskyとは、前置きで「できない」と書いた、リポジトリごとのフックを実現するスクリプトです。Huskyの少なくとも最近のバージョンでは、ローカルにcore.hooksPath
を設定することでリポジトリごとのフックを実現しているため、グローバルにcore.hooksPath
を設定する方法と競合します。Huskyを使用している環境では、Huskyによって設定されたフックしか動作しないのです。
仕方ないのでHuskyは無理矢理無効にした上で、ローカルなフックと同様に、グローバルなフックから実行する形にします。Huskyには初期化スクリプトを実行する機能があるため、そこでローカルなcore.hooksPath
をクリアすることによってグローバルな設定を有効にするというハックです。
#!/bin/bash
# ~/.config/husky/init.sh等
if git config core.hooksPath | grep -q '^\.husky'; then
git config --unset core.hooksPath
echo Huskyによる変更を修正しました。再実行してください。
exit 1
fi
これにも制限があり、Husky側に設置していないフックが実行されても初期化スクリプトは動作しないので、その場合は手動でなんとかしたりする必要があるのですが……。
とはいえ、最終的にはそれっぽくできました。
最終的には、以下のような形でGitフックをグローバルに設定しつつ、ローカルに設定することも可能にし、またHuskyが使われているリポジトリでもHuskyと併用できるようにしました。
core.hooksPath
をグローバルに設定する。core.hooksPath
をクリアして、無理矢理グローバルなcore.hooksPath
のフックを使わせる。実例として私が使っているフック回りのスクリプトを参考にしてください。
HAYASHI-Masayuki/global-git-hooks
この文書はCC BY(クリエイティブ・コモンズ表示4.0国際ライセンス)で公開します。