🎄

【Git hooks】pre-commitフック導入

2023/12/04に公開

はじめに

チーム開発をしていると、コードをプッシュする前に確認することがたくさんあります。テストが通っているか、フォーマットが揃っているか、意図しないコードが含まれていないか、正しいブランチに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という設定ファイルを使用する方法と使用しない方法があります。

  1. .pre-commit-config.yamlを使用する方法
    pre-commit ツールをインストールし、設定ファイルに使用するツールやスクリプト、バージョンなどを選んで利用する

  2. .pre-commit-config.yamlを使用しない方法
    フックスクリプトを自分でカスタムして利用する

次に、それぞれの方法についてまとめます。

1. .pre-commit-config.yamlファイルを使用する方法

ルートディレクトリに.pre-commit-config.yamlファイルを作成します。このファイルには、どのフックを実行するかや実行順序などの設定を記述します。

それぞれのフックは、この設定ファイルからフックスクリプトをダウンロードし、これを .git/hooks/ ディレクトリ内にインストールすることで有効になります。

基本的な書き方

キーと値を指定してフックの設定をします。

(例) .pre-commit-config.yaml
- 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一覧を参照してください。

2. .pre-commit-config.yamlファイルを使用しない方法

今回は

  • 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

.githooks/pre-commit
#!/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

.githooks/pre-commit
#!/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" かテストする。等しい場合は、真。

下書きの状態でコミットしたい場合もあると思うので、Enteryを押した時のみテストを実行するようにしました。

検証

わざと不要なスペースをつけてコミットしようとしたところ、画像のように実行されることが確認できました。

また、バックエンド以外のディレクトリでの変更では、pre-commitは実行されませんでした。

参考資料

https://git-scm.com/book/ja/v2/Git-のカスタマイズ-Git-フック

https://pre-commit.com/

https://zenn.dev/daifukuninja/articles/2129a2d50a3a9b

自己紹介

Sun* エンジニアのazukiです。主にRuby on Railsを使用したバックエンド開発に携わっています。現在は次世代ソリューションの新規事業創出に取り組んでおり、技術の面から価値創造を目指しています。
https://adventar.org/calendars/9043

Sun* Developers

Discussion