cangoxina

生産性向上見習いのブログ的な何かです

モブプロで役立つ??git rebaseでsquashする時にそれまでの著者をCo-authored-byに自動で追加するgit hookを作りました🎉

こんにちは。生産性向上チームで日々生産性を上げている平木場です*1

いきなりですが、みなさんモブプロしてますか?

モブプロをしていると複数人が作ったcommitをsquashでまとめる時があるのではないでしょうか?しかし、squashをすると、それまでcommitした人の貢献がなかったことになってしまいます。

そこでgit rebaseでsquashする時に、それまでの著者をCo-authored-by*2に自動で追加するgit hookを作りました。

auto-insert-co-author-githookが動作している様子


目次


背景

git rebaseでのsquashの弊害

モブプロではドライバーを交代するので、なんらかの方法でソースコードを共有する必要があります。

例えば僕が所属しているチームでは、ドライバーがソースコードをZoomの画面共有で視覚的に共有し、交代時にそれまでの作業をWIPとしてcommit、pushしてソースコードの実体も共有しています*3

この方法だとゴミのようなcommitが履歴に残ってしまうため、mainブランチにマージする段階でcommitをまとめる必要があります。その時にgit rebase -i mainでcommitをsquashします。

しかし、squashするとそれまでのcommitのauthorが文字通り押し潰されます。

何が問題なのか、例を示して説明します。 例えば、以下のような履歴があります。(上が最新)

hash message author commiter branch
34fa9e wip hoge hoge HEAD
ab1fe3 wip foo foo
5eba55 WIP fuga fuga
f12acf Merge pull request... foo github main

この状態で、ユーザーhogegit rebase -i mainして、全てのwipを5eba55にまとめると下のようになります*4

hash message author commiter branch
5eba55 feat: add feature fuga hoge HEAD
f12acf Merge pull request... foo github main

5eba55のauthorであるユーザfugaはrebase後もそのままauthorになり、rebaseをした ユーザhogeがcommitterとなりました。

この状態でgithubにpushすると、このcommitはfugaとhogeが作ったことになり、同じく貢献したはずのfooは履歴に残らず貢献していないことになってしまいます。このcommitがmainにマージされてもユーザfooに草は生えません。

エンジニアによってはやる気低下の原因になってしまいます...

Co-authored-byでauthorを複数人設定できる

ではどうしようもないのかというと、そうではありません。

GitHubには、commitメッセージを工夫することで共著者を設定できる機能があります。

下のように、commitメッセージの最後に空行を2つとCo-authord-by: 作者の名前 <メールアドレス>を追加します。

このcommitをGitHub上で見るとこうなります。

f:id:korosuke613:20200910203254p:plain
GitHubのcommitに複数のユーザアイコンが並んでいる。クリックしたらその人のアカウントに飛ぶ

commitしたユーザとCo-authored-byで追加したユーザのアイコンが並んでいますね。(わかりづらいですがアイコンが3つ並んでいます。)

このようにCo-authored-byを使うことでコミットしたユーザ以外も貢献したことにできます。 しかし、毎回毎回まとめるcommitのcommitterをcommitメッセージに追加するのは面倒ですね...

auto-insert-co-author-githook

そこで、squashするcommit履歴からcommitterを抽出し、まとめるcommitのcommitメッセージにCo-authored-byを自動で追加するgit hookを作成しました

github.com

下の画像のように、squashするcommitのcommitterが自動でCo-authord-byに追加されます。

これでみんな仲良く草を生やすことができますね🌲

f:id:korosuke613:20200910204428p:plain
commitメッセージの末尾にCo-authored-byが追記されてる

インストール

インストール方法はいくつかあります。詳細はREADME.mdをご覧ください。

  • pre-commitを利用する(とってもおすすめ🤴
  • lefthookを利用する(2番目におすすめ)
  • insert-co-author.shprepare-commit-msgにリネームして.git/hooks/に配置する(従来の方法)

特にpre-commitはすばらしく、プロジェクトルートに下記のような.pre-commit-config.yamlを配置してpre-commit install --hook-type prepare-commit-msgを実行するだけでインストールできます。非常に簡単でおすすめです。

仕組み

auto-insert-co-author-githookの本体はinsert-co-author.shです。insert-co-author.shがcommitメッセージを書き換えます。

insert-co-author.shprepare-commit-msgで起動することを想定しています。(prepare-commit-msgフックはcommitメッセージを編集する手前で呼び出されるhookです。)

insert-co-author.shが呼び出されるまでの流れ

  1. git rebase -i mainで対話モードに入るとエディタが起動し、各commitのコマンドの編集画面に遷移する。
  2. pickをsquashに書きかえ終えると、squashする古いcommitから一つずつ自動でrebaseされる。
  3. squashするcommitが全てrebaseされるとcommitメッセージを書き換えるためにエディタが起動するため、エディタの起動前にinsert-co-author.shhookが呼び出される。
  4. insert-co-author.shが動作する。

insert-co-author.shがcommitメッセージにCo-authored-byを追加する流れ

最新のコードはリポジトリを参照してください。

  1. git statusでgit rebaseの対話モードであるかどうか確認する。違ったら終了する*5
  2. .git/rebase-merge/doneファイルにあるsquashするcommitのhashを取得する。
  3. 2.で得たsquashするcommitをgit showで表示する。この時、Co-authord-by: 作者の名前 <メールアドレス>の形式になるようにフォーマットする。
  4. 3.の結果を一意にし、.git/COMMIT_EDITMSGに追記する。

squashするcommitを取得する方法(.git/rebase-merge/done)

.git/rebase-merge/doneはrebase中にgitによって作成される一時ファイルです。

このファイルにはどのcommitをpickするか、squashするかなどの内容が含まれています*6。 このファイルからsquashするcommitを取得します。(上記2.)

commitメッセージにCo-authored-byを追加する方法(.git/COMMIT_EDITMSG)

.git/COMMIT_EDITMSGはcommitメッセージの編集でエディタが起動する前にgitによって作成される一時ファイルです。

エディタが起動すると、すでに何らかの文字列が存在していると思います。 その理由は、私たちはcommitメッセージを編集時に、デフォルトの文字列が入った.git/COMMIT_EDITMSGを直接エディタで開いているためです。

f:id:korosuke613:20200910214034p:plain
commitメッセージの編集画面(vim)のステータスバーにCOMMIT_EDITMSGと表示されてる

編集後にエディタを閉じるとgitは.git/COMMIT_EDITMSGを解析してcommitメッセージとしているわけですね。

エディタの起動直前に.git/COMMIT_EDITMSGにCo-authord-byを追記するため(上記4.)、commitメッセージにCo-authord-byを追加することができます。

課題

ただ、本記事の方法では、git hookの性質上、チームメンバごとかつリポジトリごとにauto-insert-co-author-githookを設定しなければならないという課題があります。

チームメンバ全員が新しいリポジトリで作業を始めるたびにインストールの手続きをしなければならないのはそれはそれで面倒だと思います。

一応、globalなgit-hookを設定する方法もありますが、これはこれで面倒です。pre-commitの設定ファイルをグローバルにできれば少しは楽になるのですが...

また、bashで記述しているので、windowsではおそらく動作しません

ついでに、テストが十分にできていないのでバグが結構ありそうです。

作ってみて

たった数十行のシェルスクリプトですが、作るのが非常に辛かったです。

goあたりで書きたかったのですが、OSごとに実行ファイルを作る必要があり面倒だと感じたのでシェルスクリプトで記述しました。結局Windowsでは動作しないのであんまり変わらなかったのですが、気づくのが遅かったです。いつかもっと高水準な言語で書きたいですね。

git rebaseの仕組みもいまいちよくわかってなかった状態で手探りで作り始めたので、「できた 〜」と「できたと思ったけど実はできてなかったわ」を何回も繰り返しました。おかげで作る前よりほんのちょっとgitと仲良くなれた*7気がします。

正直まだまだよくわかってません。この記事でのgit用語の使い方や説明もあんまり自信ありません。

また、このhookをTwitterでつぶやいたところ、めずらしく8リツイート23いいねとプチバズりしました*8。弊社のつよつよエンジニアの方たちもリツイートしてくれたのも大きかったと思います。

正直git rebaseでsquashして僕のように困った人がどれだけいるのかさっぱりだったんですけど、まあまあ同じ悩みを持ってる人がいるっぽいことがわかってよかったです。

ぜひ使ってみてフィードバックください!


*1:社内ではきばちゃんと呼ばれてます

*2:commitメッセージに追記することで共著者として登録できる。 https://docs.github.com/en/github/committing-changes-to-your-project/creating-a-commit-with-multiple-authors#creating-co-authored-commits-on-the-command-line

*3:僕たちはもっぱら「WIPを積む」と表現します

*4:ついでにcommitメッセージを意味のあるものにしてます

*5: prepare-commit-msg hookはgit rebase -i以外のcommitメッセージを編集する時も呼び出されるため。

*6:たぶん厳密には違う

*7:git完全に理解した

*8:僕にとってはなかなか多い数字なのです。


スポンサーリンク