Git のクライアントサイドフックを使ってコミット時に自動フォーマットなどを行う
Git には Hooks という仕組みがあり、git commit
コマンドを実行した時とかに自動実行するスクリプトを設定できる。
- 参考 : Gitのフックの説明と挙動の検証 - Qiita … フックの種類
今回はこの仕組を使って、pre-commit
のタイミング、つまり git commit
コマンド実行時に自動フォーマットをかけたりしてみる。
フックスクリプトはどこにある?
フックを設定するためのスクリプトがどこにあるかというと、Git プロジェクト配下の ./.git/hooks/
配下にある。
デフォルトではココに pre-commit.sample
といったファイルが置いてあるかと思う。実際にフックを有効にするには pre-commit
というファイル名にリネームすることで使えるようになる。
今回は pre-commit.sample
はそのままに、空の pre-commit
ファイルを置いてみる。
$ cd ./.git/hooks/
$ touch pre-commit
pre-commit
フックに限らず、クライアントサイドフックのスクリプトは通常 Git 管理の対象外 (.git/
ディレクトリ配下だし…) なので、チーム開発で使う場合は何らかの方法でフックスクリプトの導入を徹底させる必要がある。
- 参考 : 多人数開発で Git を使う場合の環境構築 | GREE Engineers' Blog … 各自の開発環境の、特定のディレクトリに置いたファイルを設置し、それを参照するイメージ。
- npm-scripts の
post install
とかでそういうシェルスクリプトを実行するとかで対応しても良いかと思う。
このファイルを色々と書き換えてフックスクリプトを用意し、後で実際に git commit
コマンドを叩いて検証してみる。
コミット時に TextLint の自動修正を行う
まずは簡単な例。コミット時に TextLint の自動修正を行い、文章を修正してみようと思う。
対象のプロジェクトには textlint
および Auto-Fix 可能なルールプラグインを入れておき、npm run textlint
で textlint
コマンドが使えるようにしておこう。
次に、pre-commit
ファイルに以下のように書き込む。中身は Bash スクリプトだ。
#!/bin/bash
for FILE in `git diff --cached --name-only | grep .md`; do
npm run textlint -- --fix $FILE
git add $FILE
done
ヒアドキュメントを使ってコマンドラインから書いても良いかも。
$ cat << EOL > ./.git/hooks/pre-commit
#!/bin/bash
for FILE in `git diff --cached --name-only | grep .md`; do
npm run textlint -- --fix $FILE
git add $FILE
done
EOL
この書き方知ってるとスゴイ人っぽい。w
さて、このファイルで何をやっているかというと、git diff --cached --name-only
(--cached
は --staged
と同じ) で差分のあるファイルの名前を抽出し、.md
(Markdown) ファイルのみを抽出する。そしてそれらに対して順に textlint を実行し、再度 git add
している。
--cached
と書いておけば新規作成したファイルも対象になる。変更後は再度 git add
しないことには変更がコミットされないのでこうなっている。
textlint
コマンドについては、textlint --fix [File]
という書式がデフォだが、npm run
でオプション引数を渡すために間に --
が入っている。
とてもシンプルなスクリプトだが、ファイル名にスペースが含まれているとうまく展開できなくておかしくなるので、この次のサンプルで直すとする。
また、「Auto-Fix できない Lint エラーがあるならコミットしたくない」という場合にも対応できるよう、もう少し直してみる。
TextLint チェックエラーがある場合はコミットさせない
TextLint チェックエラーがある場合はコミットさせないよう、終了コードを設定してみよう。pre-commit
コミットフックは終了コードに 0
以外を渡すと、コミットメッセージを入力する画面に移動せず、処理を中断してくれる。
#!/bin/bash
# 終了コード
IS_ERROR=0
# $FILE でスペース区切りのファイルを受け取れるよう、区切り文字を改行のみにする
IFS=$'\n'
for FILE in `git diff --cached --name-only | grep .md`; do
# Auto-Fix できそうなモノを先に直す
npm run textlint -- --fix "$FILE"
# それでもエラーがあれば終了コードを 1 に設定する
if ! npm run textlint "$FILE"; then
IS_ERROR=1
fi
git add "$FILE"
done
if [ $IS_ERROR -eq 1 ] ; then
echo エラーがあります。修正してからコミットしてください。
fi
exit $IS_ERROR
途中に IFS=$'\n'
という行があるが、この設定によって、スペース区切りのファイルも for ... in
で扱えるようになっている。IFS は Internal Field Separator の略らしい。
TextLint の場合、先に --fix
オプションで Auto-Fix してから、純粋なエラーチェックだけを再実施している。if ! 【コマンド】
で、コマンドの実行結果がエラーの場合に終了コード用変数を書き換えるようにした。
- 参考 : gitのpre-commit hookを使って、綺麗なPHPファイルしかコミットできないようにする - MANA-DOT … 終了コードの変え方を参考にした
- 参考 : git hookを使ってデバッグコードをコミットできないようにする - はむはむエンジニアぶろぐ
だんだんシェルスクリプトを書くのがつらくなってきた…。無理せず、ちょっとずつ必要なフックを入れていこう。
- 参考 : gitコミット時にデバッグコードが含まれていないかをチェックする - Qiita … フックスクリプトは Ruby などで書いても良い。その方が楽になりそう。
- 参考 : GitHub - observing/pre-commit: Automatically installs a git pre-commit script in your git repository which runs your `npm test` on pre-commit …
pre-commit
を実現する npm パッケージ。 - 参考 : Gitのコミットフックでprettierの強制フォーマットがかかるようにする - Qiita … lint-staged という npm パッケージでファイルの種類別に Lint チェックを行える。
- 参考 : Git Hooks管理を簡単に - overcommitを使おう - Qiita … overcommit。フックの管理に使える Ruby 製のツール。