【Git hooks】pre-commitフック導入
はじめに
チーム開発をしていると、コードをプッシュする前に確認することがたくさんあります。テストが通っているか、フォーマットが揃っているか、意図しないコードが含まれていないか、正しいブランチにpushしようとしているか...毎回これらを一つ一つ確認するのは大変です。
そんな時、Git hooks の pre-commitフック が便利です。本記事では、pre-commitフックの基本的な概念から導入方法までをまとめ、実際にpre-commitでrspecとrubocopを実行できるようにします。
本記事は Sun* のアドベントカレンダーに寄稿するために書きました。
🎄 Sun* Advent Calendar 2023
pre-commitフックとは
Git hooksは、特定のイベントが発生する前に実行されるスクリプトを指します。その中で pre-commitフックは、コミットが実行される前に呼び出され、事前に定義された処理や検証を行います。
メリット
コミットが実行される前にコードを確認し、問題があればコミットを拒否することで、品質向上とエラーの早期発見を促進します。
デメリット
コミットごとにフックが実行され、その処理を待つことになるので、コミット作業のパフォーマンスとコストに影響を与える可能性があります。
pre-commitフックの導入
ご自身のプロジェクトの.git/hooks
を見てみてください。
これらのサンプルはファイルの名前から.sample
を削除することで有効できます。
下コマンドで実行権限を付与すれば、commit時に実行されるようになります。
chmod +x .git/hooks/pre-commit
pre-commitを利用する2つの方法
pre-commitを利用するには、.pre-commit-config.yaml
という設定ファイルを使用する方法と使用しない方法があります。
-
.pre-commit-config.yaml
を使用する方法
pre-commit ツールをインストールし、設定ファイルに使用するツールやスクリプト、バージョンなどを選んで利用する -
.pre-commit-config.yaml
を使用しない方法
フックスクリプトを自分でカスタムして利用する
次に、それぞれの方法についてまとめます。
.pre-commit-config.yaml
ファイルを使用する方法
1. ルートディレクトリに.pre-commit-config.yaml
ファイルを作成します。このファイルには、どのフックを実行するかや実行順序などの設定を記述します。
それぞれのフックは、この設定ファイルからフックスクリプトをダウンロードし、これを .git/hooks/
ディレクトリ内にインストールすることで有効になります。
基本的な書き方
キーと値を指定してフックの設定をします。
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
キー | 説明 |
---|---|
repo: |
フックを提供しているリポジトリのURL - 🔗リポジトリ一覧 |
rev: |
取得するリポジトリのバージョン |
hooks: |
取得するフックのリスト |
例で使用したtrailing-whitespace
フックはコード中の不要な末尾の空白を検出し、end-of-file-fixer
フックは各ファイルの末尾に改行を追加するなどの処理を行います。
フックの一覧については🔗pre-commit-hooksのID一覧を参照してください。
.pre-commit-config.yaml
ファイルを使用しない方法
2. 今回は
- a. pre-commitでrubocop
- b. pre-commitでrspec
を実行できるようにします。
※ pre-commit.sample
に書かれていた内容は、rubocopが有効になれば不要になる内容だったので削除しています。
pre-commitフックをリポジトリ管理する
.gitディレクトリではリポジトリ管理がされないので、チーム開発をする際は、別のディレクトリを作成し使用します。
.
├── .git
└── .githooks
└── pre-commit
下コマンドで Git hooks の参照先ディレクトリを変更できます。あと、実行権限も付与しておきます。
git config --local core.hooksPath .githooks
chmod +x .githooks/pre-commit
a. pre-commitでrubocop
#!/bin/sh
# server/ 配下に変更があるか
if git diff --quiet --exit-code -- ./server; then
echo "Running rubocop ..."
docker-compose -f ./server/docker-compose.yml exec -T web bundle exec rubocop -A
else
echo "No changes detected in ./server"
fi
コードの解説
-
git diff
: Gitの変更を表示するコマンド -
--quiet
: 変更があった場合にのみ終了コードを返す -
--exit-code
: 終了コードが存在する場合はゼロを返す -
-T
: 非対話的なコマンドを実行する際のオプション -
rubocop -A
: -Aオプションは自動整形できるものはやる
バックエンドとフロントエンドを同じリポジトリで管理しているので、条件分岐をしてバックエンド側の変更があった時のみrubocopを実行するようにしました。
b. pre-commitでrspec
#!/bin/sh
...
else
...
# 標準入力を受け取る
exec < /dev/tty
# rspecの実行をするか選択
echo "Do you want to run rspec? (Press Enter for yes, or 'n' for no): "
read run_rspec
if [ -z "$run_rspec" ] || [ "$run_rspec" = "y" ]; then
echo "Running rspec ..."
docker-compose -f ./master/server/docker-compose.yml exec -T web bundle exec rspec
else
echo "Skipping rspec."
fi
fi
コードの解説
-
[ -z "$run_rspec" ]
: 変数 run_rspec が空(長さがゼロ)かテストする。空であれば(-z)、真。 -
[ "$run_rspec" = "y" ]
: 変数 run_rspec が文字列 "y" かテストする。等しい場合は、真。
下書きの状態でコミットしたい場合もあると思うので、Enter
かy
を押した時のみテストを実行するようにしました。
検証
わざと不要なスペースをつけてコミットしようとしたところ、画像のように実行されることが確認できました。
また、バックエンド以外のディレクトリでの変更では、pre-commitは実行されませんでした。
参考資料
自己紹介
Sun* エンジニアのazukiです。主にRuby on Railsを使用したバックエンド開発に携わっています。現在は次世代ソリューションの新規事業創出に取り組んでおり、技術の面から価値創造を目指しています。
Discussion