🤖

「commit messageを考えてる間によしなに git hooks を実行しておいてね!」を実現するneovimの設定をかいた

2023/08/16に公開

TL;DR

  • Git の commit message を考えている間に pre-commit script を実行するneovimの設定を書いた
  • CLIから git commit -n で neovim を立ち上げて使う
  • 今はいろいろ雑なので、今後は設定を切り出してプラグイン化/CLI Tool化したい

はじめに

ある日、いつものようにThe Engineers' Paradiseで喋っていると、da-ja-re王ことkuuさんgitcommit の設定が話題になりました。

https://github.com/kuuote/dotvim/blob/535d6d33e964a46c39076b306ae9f58d59f8e8b5/ftplugin/gitcommit.vim#L20-L37

この設定は、「gitのcommit messageを打っている間に .git/vim にあるスクリプトを実行してくれる」設定です。

「確かにcommit message を考えている間にスクリプトを実行できたら嬉しいだろうな」
「これをgit の git hooks の実行に使えたら!」

ということで、このアイデアを実現する方法を考えてみました。

そもそも git hooks ってなに?

git hooks とは、git のコマンドを実行する前後に実行されるスクリプトです。
hooks にはいくつか種類がありますが、commit前後に実行されるスクリプトに限れば2つあります。

  • pre-commit: commit前に実行されるスクリプト。例えばformatがかかっているかどうかをチェックしたり、lintをかけたりするのに使われる
  • commit-msg: commit messageを書いた後に実行されるスクリプト。commit messageのフォーマットが正しいかどうかをチェックする

さて、これらのスクリプトは、commit を行うたびに実行されます。
で、スクリプトの処理内容次第ではしばしば長時間待たされることがあります(prettier とか eslint とか。もちろん lint-staged 等と組み合わせれば短くすることもできるでしょうが)。
じゃあスクリプトの終了を待っている間に同時にcommit messageを編集できたら嬉しいよね、というのが本記事の趣旨です。

git commit実行時のhookの実行順序

例えば、git commitを実行した時

pre-commitが実行される
↓
editor (vim など) が立ち上がるので、commit message を記入する
↓
commit-msgが実行される
↓
commitが実行される

という流れになる。

また、 git commit -m 'hoge' というように -m オプションをつけてcommit messageを指定した場合は、

commit-msgが実行される
↓
pre-commitが実行される
↓
commitが実行される

という流れになる。

いずれの場合も、commit messageを書いている間にpre-commitが実行されることはない。

git hooks の設定について

git hooks を設定するためのパッケージとしては huskysimple-git-hooks が有名です。
しかし、実はGit 2.9 以降ではGit のcore.hooksPath オプションを利用すると追加依存なしで Git Hooks を利用することができます。

例えば

mkdir -p .githooks
touch .githooks/pre-commit
chmod +x .githooks/pre-commit
git config --local core.hooksPath .githooks

を実行すると、.githooks/pre-commit がcommit前に実行されるようになります。

詳しくは以下の記事をご覧ください。
https://efcl.info/2021/08/21/git-precommit-hook/

本記事で用いているgit hooks はcore.hooksPath で指定されたものを使っています。

どうやって実現する?

最初は、CLI Toolを書こうとしていました。
なぜなら、自分はCLIからgit commit -m 'hoge' でgit commit する人間だからです。
しかし、CLI Toolを書くとなると、いろいろと面倒なことがあります。
コンパイルしたり、ライブラリを選定したり、面倒くさいです。
特に今回はスクリプトを実行して結果を得る必要があるので、画面を分割したり非同期処理をしたりと考えることがまあまああります。

そこで、今回はNeovimの設定として書いてしまうことにしました。
Neovimの設定なら、設定ファイルに書けばすぐ実行できます。
また、標準のAPIや関数を呼び出すことで、画面分割も非同期処理も簡単にできます。
いいね 👍

お披露目

というわけで、Neovimの設定を書きました。
以下の動画で実際に動いている様子を見ることができます!!

https://youtu.be/VetyhcZ_rlI

以下に設定を載せておきます!
Neovim使いの方はこれを after/ftplugin/gitcommit.lua に記述してください。

https://github.com/ryoppippi/dotfiles/blob/fba410346e51e128a590d17d22bb1d439110c5c3/nvim/after/ftplugin/gitcommit.lua

簡単に何をしているか解説すると

  • git commit -n で Neovim を立ち上げる。こうすることで、git コマンドは hooks を実行しない
  • もし pre-commit hook が存在しているならば画面を分割してターミナルを開き、スクリプトを実行する
  • もし commit-msg hook が存在しているならば、 COMMIT_EDITMSG ファイルが保存されるたびにcommit-msg を実行する
  • もし どちらかの hook が失敗しているのにも関わらず Neovim を終了しようとした場合、 :cquit コマンドで neovim を異常終了とすることで commit されることを防ぐ
  • もしどちらの hook もパスしていた場合、:q コマンドで Neovim を終了することで commit される

という流れになっています。
これで、commit message を書いている間にスクリプトを実行することができました!

Future Works

自分のユースケースとしてはこの設定でうまくいくのですが、これをいい感じにまとめて行きたいなと思っています。

具体的には、

  • gin.vim など、vim 内でcommit を行う場合にも使い物になるようにしたい - 現状 hooks がpass していない状態でbuffer を閉じると :cquit でneovim が終了されてしまう。autocmd 等でうまく設定すればいいのかもしれない。
  • plugin として公開したい - そもそも設定の中に :cquit が入っている時点で現状では万人にお勧めできるものではなさそう
  • vim でも動くようにしたい - luaで書いているので vim では動きません。 Denops で書き直したい
  • CLI Tool として公開したい - vim/neovim に限らずにこれをやりたい人はいそう。エディタに囚われないCLI Tool として形にしたい(時間の関係で間に合わなかった & go/rust の勉強がおっついていない)

というわけで、現状色々と課題はありますが、ひとまず動くものができたのは満足です。

終わりに

  • kuu さんありがとうございました。
  • 楽園 でお会いしましょう。

おまけ

git config --global commit.verbose true

を設定しておくと、COMMIT_EDITMSG ファイルに git status と git diff が表示されるようになります。

git config --global commit.verbose false
git config --global commit.verbose false の場合
git config --global commit.verbose true
git config --global commit.verbose true の場合

この設定により1つプラグインを減らすことができ、Neovim の起動がまた少し早くなりました。

(@atusy さんに教えてもらいました。ありがとうございました)

Discussion