10
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

リンクアンドモチベーションAdvent Calendar 2024

Day 15

コミット時に自動でコードをチェックしてほしい!

Last updated at Posted at 2024-12-14

はじめに

コードをプッシュした後に、「あ、フォーマットかけるの忘れてた!」と思い出し、フォーマット修正とかyarn fix適用みたいなコミットをすることはありませんか?

自分は稀によくあります(^_^;)

チーム開発に携わるようになって、こうしたコミットをしないことの重要性がわかりました。例えば、最後のコミットがフォーマット修正系のコミットだと、何の変更をしたのかがわかりづらくなるといった問題があります。

(↓ 弊社の先輩の記事でも、コミットにこだわることの重要性が述べられています)

今回はコミット時に自動でそうしたチェックを行う仕組みを作る方法を調べました。

前提

自分が実現したいことは以下です。

  • コミット前にeslintprettierのチェックをして、引っかかるコードがあったらコミットを中断する
    • →フロントエンド開発をしているため
  • モノレポ構成のプロジェクトに対応できる
    • →開発しているプロジェクトがそうなっているため
  • GitHubにアップロードされるファイルは変更せず上記の要件を実現する
    • →あくまでも個人用途のため、このためのライブラリ(Huskyとか)をプロジェクトに導入したり、.gitignoreに追記したりとかもあまりしたくないなあと思ったため

pre-commitを探る

表題のようなニーズを実現するツールとしては、pre-commitが有名です。調べたら一番出てきます。
インストールをした後、.pre-commit-config.yamlという設定ファイルにチェックしたい項目を記述していき使用するようです。

この記事ではpre-commitのインストール方法や基本的な設定方法の説明は省略します。

eslintprettierといった外部のツールを使ったチェックをしたい場合は、pre-commitで使う用のプライグインのようなものが開発されているので、それを設定ファイルで指定します。
(pre-commitから提供されているものや、有志が開発しているものがあります。eslintprettierはメジャーだからか、pre-commitから提供されているものがあります!)

↓例えばeslintを使う場合はこんな感じです。

.pre-commit-config.yaml
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?$

今回はprettiereslintを使いたいので、それぞれのレポジトリを見てみます。
ですが、mirrors-prettier (pre-commitから提供されているprettierのプラグイン)をみてみると…

image.png

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を使う設定などをしていました。

こんな感じのファイルでした👀
.git/hooks/pre-commit
#!/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では、外部のスクリプトを読み込んで実行させることにします。

.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をチェックするスクリプトを記述します。
(ここでディレクトリを移動する処理を書いたりすれば、モノレポ構成のレポジトリにも対応できますね!)

.git-hooks/pre-commit
#!/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ディレクトリが追跡されなくなります。
全てのレポジトリに影響があるので、気をつけてください。

~/.config/git/ignore
.git-hooks

反映されない場合は、~/.gitconfigに違うファイルをグローバルな.gitignoreとして読み込むように設定されているかもしれません。~/.gitconfigファイルを見てみてください。excludesfileが追跡対象外になるものを指定するファイルです。

おわりに

pre-commitのmirros-prettierが使えないのは残念でしたが、おかげでGitのさまざまな標準機能の存在を知ることができました。
Gitの知らない機能はまだまだありそうです。毎日使うツールなので最大限活用していきたいですね!

読んでいただきありがとうございましたm(_ _)m

参考

10
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?