Goのプロジェクトでpre-commit設定をチーム共有する
やりたいこと
普段Goでチーム開発しているが、 人によってコードのフォーマット(インデントなど)が異なることを避けるためにLinterとFormatterを自動でかけるようにしたい。
いつかけるか
一応今までもIDEで自動でかけようということにはなっていたのだが、微妙な設定の違いなどでうまくかかっていないコードがpushされることがあった。 こういうのは開発で必ず行うcommit前に自動でやってしまうのがいい。 ということでgit のhook機能のひとつ、pre-commitを使うことでcommit前に設定したスクリプトでLinterとFormatterを実行することにした。
ちょっと問題が
pre-commitでLinterとFormatterをかけるのはできる。
だが、 .git
ディレクトリの中に pre-commit
という名前でスクリプトを置かないといけない。
そして、.git
の中身はGithubで共有できない。
開発メンバー全員がこの設定を使うようにしたいので工夫が必要だ。
環境変数の共有にdirenvを使っていた
我々のチームではリポジトリごとの環境変数の共有にdirenvを使っていた。
これは主に、ディレクトリごとに環境変数を定義して、そのディレクトリに移動したときだけ環境変数を有効にする用途で使う。
direnvは .envrc
に export HELLO=WORLD
のようにして書いた環境変数がbashによって設定されるが、
普通に cp
などのコマンドも使えるので今回はそれを使うことにした。
プロジェクトルートのディレクトリに入ったらpre-commitが設定される
pre-commitスクリプトは .pre-commit
という名前でプロジェクトルートに配置した。
これでgithubでの共有ができる。
しかしこのままではgitがpre-commitスクリプトとして使ってくれないので、 .git/hooks/pre-commit
にコピーする必要がある。
そこで先に述べた direnv
を使う。
.envrc
に cp .pre-commit ./.git/hooks/pre-commit
と書けばそのディレクトリに入った時点でコピーされるので目的が達成できる。
pre-commitを有効にするには git init
も必要なので、 .envrc
の中身は以下のようになる。
cp -a ./.pre-commit .git/hooks/pre-commit
git init
これでディレクトリの中に入ればpre-commitが設定され、コミット時にLinterとFormatterを走らせることができる。
pre-commitスクリプトの中身
実際のpre-commitスクリプトは、 goimports
と go vet
を走らせる処理を書いている。
まず差分があるgoファイルがあるディレクトリ名を取得し、先頭に ./
を付けている。 go vet
ではこれがないと対象を認識してくれなかった。
その後、各ディレクトリに go vet
をかけ、問題なかったら強制的に goimports
によるformatterかけをしてaddする。
これがcommitをする前に走るようになるので、全てのcommitのフォーマットが統一され、linterもかかっているので問題のあるコードがcommitされにくくなる。 CI/CDのときにチャックする方法もあるだろうけど、こっちの方が全commitが綺麗になるしいい気がした。
#!/bin/sh
echo "Info: Runnning linter and formatter"
gopackages=$(git diff --cached --name-only --diff-filter=ACM | grep '.go$'| xargs -n1 dirname| sort -u| sed 's/^/.\//' )
[ -z "$gopackages" ] && exit 0
echo "Info: git diff(package)"
echo $gopackages
echo "Info: Runnning go vet"
linterr="$(go vet $gopackages 2>&1 )"
if [ -n "$linterr" ] ; then
echo "fatal:"
echo $linterr
exit 1
fi
echo "Info: go vet --- OK"
unformatted=$(goimports -l $gopackages 2>&1 )
if [ -n "$unformatted" ] ; then
echo "Info: unformatted file detected"
echo "Info: $unformatted"
echo "Info: Running goimports."
for fn in $unformatted; do
goimports -w $fn
echo >&2 "goimports -w $fn"
git add $fn
done
fi
echo "Info: goinports --- OK"
exit 0
もし何か質問やフィードバックがありましたら、 @biosugar0 までお願いします。