はじめに
この記事はリンクアンドモチベーションアドベントカレンダー2024の15日目の記事です。
コードをプッシュした後に、「あ、フォーマットかけるの忘れてた!」と思い出し、フォーマット修正
とかyarn fix適用
みたいなコミットをすることはありませんか?
自分は稀によくあります(^_^;)
チーム開発に携わるようになって、こうしたコミットをしないことの重要性がわかりました。例えば、最後のコミットがフォーマット修正系のコミットだと、何の変更をしたのかがわかりづらくなるといった問題があります。
(↓ 弊社の先輩の記事でも、コミットにこだわることの重要性が述べられています)
今回はコミット時に自動でそうしたチェックを行う仕組みを作る方法を調べました。
前提
自分が実現したいことは以下です。
- コミット前に
eslint
やprettier
のチェックをして、引っかかるコードがあったらコミットを中断する- →フロントエンド開発をしているため
- モノレポ構成のプロジェクトに対応できる
- →開発しているプロジェクトがそうなっているため
- GitHubにアップロードされるファイルは変更せず上記の要件を実現する
- →あくまでも個人用途のため、このためのライブラリ(Huskyとか)をプロジェクトに導入したり、
.gitignore
に追記したりとかもあまりしたくないなあと思ったため
- →あくまでも個人用途のため、このためのライブラリ(Huskyとか)をプロジェクトに導入したり、
pre-commitを探る
表題のようなニーズを実現するツールとしては、pre-commitが有名です。調べたら一番出てきます。
インストールをした後、.pre-commit-config.yaml
という設定ファイルにチェックしたい項目を記述していき使用するようです。
この記事ではpre-commitのインストール方法や基本的な設定方法の説明は省略します。
eslint
やprettier
といった外部のツールを使ったチェックをしたい場合は、pre-commitで使う用のプライグインのようなものが開発されているので、それを設定ファイルで指定します。
(pre-commitから提供されているものや、有志が開発しているものがあります。eslint
やprettier
はメジャーだからか、pre-commitから提供されているものがあります!)
↓例えばeslint
を使う場合はこんな感じです。
repos:
- repo: https://github.com/pre-commit/mirrors-eslint # レポジトリのURLを指定
rev: v8.51.0
hooks:
- id: eslint
name: eslint
entry: eslint
language: node
types: [javascript, typescript]
files: \.jsx?$|\.tsx?$
今回はprettier
とeslint
を使いたいので、それぞれのレポジトリを見てみます。
ですが、mirrors-prettier (pre-commitから提供されているprettierのプラグイン)をみてみると…
public archiveになってる!
prettier made some changes that breaks plugins entirely
なるほど。mirros-prettierを使うことはできなさそう。
軽く調べてみた感じ有志で開発されているものもなさそうでした。(あったらごめんなさい)
自分でやってみる
pre-commitは、GitHooksというGit標準機能を使っていることが調べていて分かったので、GitHooksを使って、pre-commitみたいなことができるようにします。(ツールとしてのpre-commitと、GitHooksのpre-commitがどちらも出てくるので、やや混乱しました)
GitHooksとは
コミット時や、プッシュ時など、さまざまなGitの操作をトリガーにスクリプトを実行することができる機能です。
.git/hooks
下に操作に対応したファイルが入っています。今回はこの中のpre-commit
ファイルを編集していきます。
ツールのほうのpre-commitをレポジトリにインストールすると、.git/hooks/pre-commit
が書き換えられます。そこで.pre-commit-config.yaml
を使う設定などをしていました。
こんな感じのファイルでした👀
#!/usr/bin/env bash
# File generated by pre-commit: https://pre-commit.com
# ID: *一応置き換え
# start templated
INSTALL_PYTHON=/opt/homebrew/opt/pre-commit/libexec/bin/python3.13
ARGS=(hook-impl --config=.pre-commit-config.yaml --hook-type=pre-commit)
# end templated
HERE="$(cd "$(dirname "$0")" && pwd)"
ARGS+=(--hook-dir "$HERE" -- "$@")
if [ -x "$INSTALL_PYTHON" ]; then
exec "$INSTALL_PYTHON" -mpre_commit "${ARGS[@]}"
elif command -v pre-commit > /dev/null; then
exec pre-commit "${ARGS[@]}"
else
echo '`pre-commit` not found. Did you forget to activate your virtualenv?' 1>&2
exit 1
fi
```
</details>
pre-commitを編集する
.git/hooks
ディレクトリ下にpre-commit.sample
というファイルがあるはずです。このファイル名をpre-commit
に修正すると、コミット時に記述されているスクリプトが実行されるようになります。
.git
ディレクトリはデフォルトの設定だとVSCodeに出てこないし、表示させるようにして意図せず触ってしまったということがあったら嫌なので、.git/hooks/pre-commit
では、外部のスクリプトを読み込んで実行させることにします。
#!/bin/sh
# 外部スクリプトのパス
HOOK_SCRIPT=".git-hooks/pre-commit"
if [ -x "$HOOK_SCRIPT" ]; then
"$HOOK_SCRIPT"
else
echo "Skipped: $HOOK_SCRIPT not found or not executable."
fi
指定したパスのスクリプトが存在すれば実行し、存在しなければスキップします。
pre-commitで読み込む外部ファイルを作成
先ほど$HOOK_SCRIPTで指定したファイルを作成します。
例として、シンプルにlintとformatをチェックするスクリプトを記述します。
(ここでディレクトリを移動する処理を書いたりすれば、モノレポ構成のレポジトリにも対応できますね!)
#!/bin/sh
pnpm eslint
pnpm prettier --check src/
そしてこのスクリプトに実行権限を与えましょう。
chmod +x .git-hooks/pre-commit
コミットしてみる
テストとして、あえてlintが通らない状態でコミットしてみます。
# ...(前略)
4:7 error 'hi' is assigned a value but never used @typescript-eslint/no-unused-vars
✖ 1 problem (1 error, 0 warnings)
Checking formatting...
[error] src/components/WelcomeItem.vue: SyntaxError: Unexpected closing tag "div". It may happen when the tag has already been closed by another tag. For more info see https://www.w3.org/TR/html5/syntax.html#closing-elements-that-have-implied-end-tags (16:3)
[error] 14 | <slot></slot>
[error] 15 | </div>
[error] > 16 | </div>
[error] | ^^^^^^
[error] 17 | </template>
[error] 18 |
[error] 19 | <style scoped>
# ...(後略)
コミット失敗しました!成功です🤗
もしうまくいかない場合は、実行権限が付与されていない可能性があるので、.git/hooks/pre-commit
と.git-hooks/pre-commit
どちらにも実行権限があるか確認してみてください。
今回は極限までスクリプトをシンプルにしましたが、わかりやすいメッセージを出力させるようにしたり、出力したメッセージをスタイリングしたりすると、さらに使いやすくなりそうです。
また、今はチェック対象を全てのファイルにしていますが、ステージングされているファイルのみをチェックするといった工夫もできますね。
おまけ: Gitの標準機能を使ってさらに便利に
1. テンプレートにする
今回作成した.git/hooks/pre-commit
をテンプレートに登録して、今後Gitレポジトリを作成した際に、.git/hooks/pre-commit
が一緒に作成されるようにします。
ⅰ. テンプレートファイルの作成
~/.git-templates/hooks
ディレクトリを作成し、その中にpre-commit
ファイルを作りましょう。ファイルに、今回書いたスクリプトを転記します。(.git-hooks/pre-commit
の方ではなく、.git/hooks/pre-commit
の方です)
ⅱ. 実行権限の付与
保存できたら、このファイルにも実行権限を付与します。
chmod +x ~/.git-templates/hooks/pre-commit
ⅲ. テンプレートに登録
そして、.git-templates
ディレクトリをgitのtemplateとして設定しましょう!
git config --global init.templatedir "~/.git-templates"
この設定をした後にGitレポジトリを作成すると最初から.git/hooks
下にpre-commit
ファイルが登録されています。
.git-hook/pre-commit
ファイルを作って、コミット前にチェックしたい項目を記述すれば、適用されます。(実行権限の付与もしてください!)
2. 設定ファイルをignoreする
ここまでの設定だけだと、.git-hooks/pre-commit
がGitの追跡対象となり、レポジトリにプッシュされてしまいます。
前提でも書きましたが、GitHubにアップロードするコードは変更したくないので、このディレクトリを、.gitignore
を使わずにignoreさせます。
やり方
~/.config/git/ignore
を作成します。
ここに.gitignore
に書くように追跡対象から外したい対象を記述し、保存するとそのPC上の全てのGitレポジトリで記述した対象が追跡されなくなります。
例えば以下のように記述すると、.git-hooks
ディレクトリが追跡されなくなります。
全てのレポジトリに影響があるので、気をつけてください。
.git-hooks
反映されない場合は、~/.gitconfig
に違うファイルをグローバルな.gitignore
として読み込むように設定されているかもしれません。~/.gitconfig
ファイルを見てみてください。excludesfile
が追跡対象外になるものを指定するファイルです。
おわりに
pre-commitのmirros-prettierが使えないのは残念でしたが、おかげでGitのさまざまな標準機能の存在を知ることができました。
Gitの知らない機能はまだまだありそうです。毎日使うツールなので最大限活用していきたいですね!
読んでいただきありがとうございましたm(_ _)m
参考