formatが通らないとcommitできないようにした話

この記事は[セーフィー株式会社 Advent Calendar 2022](https://qiita.com/advent-calendar/2022/safie) の12月7日の記事です！
弊社で初めてのアドベントカレンダーの1週間目を迎えています〜！

さて、本題ですが今回はタイトルの通り、formatが通らない限りはcommitできないようにしたお話をしていこうと思います。

開発でなんらかのフォーマッターを使っている方なら、どなたでも読んでいただけると嬉しいです！

# モチベーション
セーフィーではバックエンドをPythonで実装していて、コードのフォーマッターに[black](https://black.readthedocs.io/en/stable/index.html)と[flake8](https://flake8.pycqa.org/en/latest/index.html#quickstart)を使用しています。<br>

これらのフォーマットのチェックはPR作成時、PRブランチへのpush時などにCIでチェックを行なっています。そのため、pushしたあとにformatが落ちていることに気が付くということがかなりの頻度でありました。。formatしてからpushしなさい！という話かもしれませんが、人間なので忘れてしまうことはありますよねぇ。。<br>

そしておそらく自分だけではなく他のメンバーも同じことを経験しているのでは？とコードレビューをしながら思っていました。<br>

**それならpush前にformatチェックしてしまえばええやん！**

# 検討ツール
pushする前には必ずcommitするので、**formatが通らないとcommitできない**ようにすれば今回の要望は叶うので、この条件でツールを探しました。

- [husky](https://github.com/typicode/husky)
node.js製のpre-committerで、jsonファイルで定義したformatをcommit前に実行してくれる。

- [pre-commit](https://pre-commit.com/)
python製のpre-committerで、ymlファイルで定義したformatをcommit前に実行してくれる。

正直他にもたくさんあるのですが、今回はツールの紹介ではないのでこの2つに絞って検討します！
huskyは[lint-staged](https://github.com/okonet/lint-staged)と連携することで、フォーマットを実行して変更ファイルをstage(git add)し、commitまでしてくれるようになるのでかなり魅力的でした。ですが、今回はpythonのプロジェクトにpre-committerを導入したいので、そのためだけにpackage.jsonを生成するのが億劫だったのでやめました。<br>

一方、pre-commitはpipでインストールできるので、pythonの環境に導入しやすいためこちらに決めました！

# pre-commitの設定方法
インストールと実行までをお話しします。
公式ドキュメントを見ながら15分くらいで簡単に設定できました。
今回pre-committerを導入するリポジトリはpythonのライブラリをpoetryで管理しているため、poetry環境を前提として話します。

1.poetryの開発環境だけにpre-commitを入れる

```
$ poetry add pre-commit --dev
```

2.設定ファイルを書く

今回はblack, flake8のチェックとホワイトスペース、末尾改行、ファイルサイズのチェックを入れます

```.pre-commit-config.yaml
repos:
-   repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v3.2.0
    hooks:
    -   id: trailing-whitespace
    -   id: end-of-file-fixer
    -   id: check-added-large-files
-   repo: https://github.com/psf/black
    rev: 22.3.0
    hooks:
    -   id: black
-   repo: https://github.com/PyCQA/flake8
    rev: 4.0.1
    hooks:
    -   id: flake8
```

3.git hook scriptsをインストール

```
$ pre-commit install
```

はい、これだけで完了です。
ちなみに、blackとflake8に引数のオプションを渡しているのですが、引数をpyprojecy.tomlで管理している場合はデフォルトでそちらが参照されるため、特に何も渡さなくて良いです。もし渡す時は、`args`を指定することで実現できますが、この場合はpyprojecy.tomlで定義した引数は無視されるのでご注意ください！
参考: https://python-poetry.org/docs/master/pre-commit-hooks/

# 動かした結果
フォーマットが正しくないファイルをcommitしようとした時は、

```
$ git commit -m invalid_format
Trim Trailing Whitespace.................................................Passed
Fix End of Files.........................................................Passed
Check for added large files..............................................Passed
black....................................................................Failed
- hook id: black
- files were modified by this hook

reformatted invalid_file.py

All done! ✨ 🍰 ✨
1 file reformatted.

flake8...................................................................Passed
```

のように表示されます。invalid_file.pyのフォーマットが正しくなく、failedとなりformatを正してくれるところまでやってくれています。この時点でcommitはされていません。
`git status`で確認すると、

```
$ git status
On branch format_test
Your branch is up to date with 'origin/format_test'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)

        modified:   invalid_file.py
```

となっているので、

```
$ git add invalid_file.py
$ git commit -m format
```

とすることでformatされた状態でcommitできます！

# まとめ
やってみたら簡単すぎて布教したくてたまらなくなってます笑
pushしてからformat落ちていることに気が付く、ということにだいぶ前から疲れていたのでもっと早く検討すればよかった、と思いました。
まだ簡単に作ってみただけなので、poetryで固定したバージョンを.pre-commit-config.ymlの反映させたり、formatしたあとに自動でgit addできるようにしてみたいな〜と考えています。
心地よい開発の環境を作るって楽しいですね！