pre-commitを使用してgit commit時にPrettierを実行させる

こんにちは。エキサイトでデザイナーをしている齋藤です。

前回、【JetBrains製エディタ対応】PrettierでTailwind CSSのclass名を規則的に自動ソーティングさせる と称して、IntelliJ IDEAでファイル保存時にPrettierを自動実行させる方法をご紹介しました。

前回の記事でご紹介した方法は、IntelliJ IDEAやVS Code特有の環境設定を用いたものでしたが、pre-commitを使用することでエディタに依存せずにPrettierを自動実行できることを業務の中で学びましたので共有します。

pre-commitとは

pre-commitとはgit commit時に定義されたシェルスクリプトなどを自動的に実行させることができるツールです。

今回は、シェルスクリプトにPrettierを実行させるスクリプトを含めて、git commitに自動的にコードフォーマットがされるようにします。

導入方法

開発環境

Spring Boot(v2.6.2)+ Thymeleafなプロジェクトです。PCはM1 Mac ProMac OS Monterey)を使用しています。

プロジェクトのディレクトリ構成

(root)
├── web
│   ├── app(Webアプリケーション本体が格納されている)
│   │    ├── src
│   │    ├── package.json
│   │    └── ...
│   └── ...
└── ...

インストール済みのnpmパッケージ

├── prettier-plugin-tailwindcss@0.6.1
├── prettier@3.3.1
└── tailwindcss@3.4.3

PCにpre-commitをインストールする

まずは、Homebrewを使用してpre-commitをインストールします。

brew install pre-commit

インストールが完了して pre-commit --versionを叩き、以下のようにバージョンが出力されれば成功です。

pre-commit 3.7.1

プロジェクトにpre-commit用のシェルスクリプトを格納するディレクトリを作成する

(root)
├── web
│   ├── app
│   │    ├── src
│   │    ├── package.json
│   │    └── ...
│   └── ...
├── git-hooks <- 追加
└── ...

パスを通す

pre-commitシェルスクリプト実行できるように、先程追加した/git-hookspre-commit用のシェルスクリプトを格納したディレクトリであることを設定します。

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

シェルスクリプトを書く

/git-hooksシェルスクリプト用のファイルとしてpre-commitを作成し、スクリプトを記述します。

├── web
│   ├── app
│   │    ├── src
│   │    ├── package.json
│   │    └── ...
│   └── ...
├── git-hooks
│   └── pre-commit <- 追加
└── ...

今回はpackage.jsonに定義したPrettier用のnpmスクリプトであるnpm run prettier:formatを実行させるスクリプトの例をご紹介します。

まずは完成形です。

#!/bin/bash

# 現在いるディレクトリを取得
initialDir=$(pwd)

# フォーマット前の変更ファイルを取得
beforeFileList=$(git diff --name-only --cached --diff-filter=d)

# Prettierを実行させるnpmスクリプトを実行
echo "Prettier format start." && cd web/app && npm run prettier:format && cd "$initialDir" && echo "Prettier format complete."

# フォーマット後の変更ファイルを取得
afterFileList="$(git diff --name-only --cached --diff-filter=d)"

# フォーマット前と後で差分がなければgit addを実行して、差分があればエラーを出現させる
if [ "$beforeFileList" = "$afterFileList" ];
then
   for file in $beforeFileList
   do
     git add $file
   done
else
   echo "Non-change files have been formatted. Please check."
   exit 1
fi

細かく説明していきます。

Bashシェルでの実行宣言

スクリプトがどのシェルで実行されるべきかを指定します。今回はBashシェルを指定します。

#!/bin/bash

現在いるディレクトリを取得

今回はnpmスクリプトを実行させるため、package.jsonが存在するディレクトリで実行させる必要があるため、ディレクトリ移動を行います。実行後に戻ってこられるように、予め、現在いるディレクトリを取得しておきます。

initialDir=$(pwd)

フォーマット前の変更ファイルを取得

ステージされている変更のうち、削除されたファイルを除いたすべての変更されたファイルの名前がリストを取得しておきます。

  • git diff : Gitリポジトリ内の変更点を表示するコマンドです
  • --name-only : 変更されたファイル名だけを表示するオプションです。変更の詳細(具体的な行の変更など)は表示されません
  • --cached : インデックスにステージされた変更だけを対象にします。つまり、git addされたけれどまだコミットされていない変更が対象です
  • --diff-filter=d : 「削除されたファイル」の変更を除外します
beforeFileList=$(git diff --name-only --cached --diff-filter=d)

Prettierを実行させるnpmスクリプトを実行

メッセージを添えて、Prettierを実行させるnpmスクリプトを記載します。

その際、package.jsonが存在するディレクトリでないとnpmスクリプトが実行できませんので、ディレクトリ移動をしたのちにnpmスクリプトを実行し、その後、予め取得しておいた現在のディレクトリに戻るようにします。

echo "Prettier format start." && cd web/app && npm run prettier:format && cd "$initialDir" && echo "Prettier format complete."

フォーマット後の変更ファイルを取得

フォーマット後の変更ファイルを取得します。

afterFileList="$(git diff --name-only --cached --diff-filter=d)"

フォーマット前と後で差分がなければgit addを実行して、差分があればエラーを出現させる

フォーマット前後のファイルリストが一致するかどうかをチェックします。一致する場合は、各ファイルをステージングします。

一致しない場合は、Prettierによるコードフォーマット以外の影響で変更が生じたことになりますので、エラーを出力してスクリプトを終了させます。

if [ "$beforeFileList" = "$afterFileList" ];
then
   for file in $beforeFileList
   do
     git add $file
   done
else
   echo "Non-change files have been formatted. Please check."
   exit 1
fi

エラーが発生してシェルスクリプトが実行できない場合

シェルスクリプトを定義してgit commitしても以下のようなエラーが発生して実行できないことがあります。

--- (エラー文) ---
hint: The 'git-hooks/pre-commit' hook was ignored because it's not set as executable.
hint: You can disable this warning with `git config advice.ignoredHook false`.

これはpre-commitに実行権限がないために発生します。

その場合は、/git-hooksに移動して実行権限を付与してください。

chmod +x pre-commit

さいごに

今回は、pre-commitを使用してgit commit時にPrettierを実行させる方法をご紹介しました。

これにより、エディタに依存しない方法でPrettierが自動実行できるようになるため、開発メンバーが個別にエディタの環境設定を変更する手間も省くことができました。

ご精読ありがとうございました。