モブプロで役立つ??git rebaseでsquashする時にそれまでの著者をCo-authored-byに自動で追加するgit hookを作りました🎉
こんにちは。生産性向上チームで日々生産性を上げている平木場です*1。
いきなりですが、みなさんモブプロしてますか?
モブプロをしていると複数人が作ったcommitをsquashでまとめる時があるのではないでしょうか?しかし、squashをすると、それまでcommitした人の貢献がなかったことになってしまいます。
そこでgit rebaseでsquashする時に、それまでの著者をCo-authored-by*2に自動で追加するgit hookを作りました。
目次
背景
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 |
この状態で、ユーザーhogeがgit 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上で見るとこうなります。
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を作成しました。
下の画像のように、squashするcommitのcommitterが自動でCo-authord-byに追加されます。
これでみんな仲良く草を生やすことができますね🌲
インストール
インストール方法はいくつかあります。詳細はREADME.mdをご覧ください。
- pre-commitを利用する(とってもおすすめ🤴)
- lefthookを利用する(2番目におすすめ)
- insert-co-author.shを
prepare-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.sh
はprepare-commit-msg
で起動することを想定しています。(prepare-commit-msg
フックはcommitメッセージを編集する手前で呼び出されるhookです。)
insert-co-author.sh
が呼び出されるまでの流れ
git rebase -i main
で対話モードに入るとエディタが起動し、各commitのコマンドの編集画面に遷移する。- pickをsquashに書きかえ終えると、squashする古いcommitから一つずつ自動でrebaseされる。
- squashするcommitが全てrebaseされるとcommitメッセージを書き換えるためにエディタが起動するため、エディタの起動前に
insert-co-author.sh
hookが呼び出される。 insert-co-author.sh
が動作する。
insert-co-author.sh
がcommitメッセージにCo-authored-byを追加する流れ
最新のコードはリポジトリを参照してください。
git status
でgit rebaseの対話モードであるかどうか確認する。違ったら終了する*5。.git/rebase-merge/done
ファイルにあるsquashするcommitのhashを取得する。2.
で得たsquashするcommitをgit showで表示する。この時、Co-authord-by: 作者の名前 <メールアドレス>
の形式になるようにフォーマットする。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
を直接エディタで開いているためです。
編集後にエディタを閉じると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用語の使い方や説明もあんまり自信ありません。
git rebase -iでコミットをまとめる際に、squashされるcommitのcommitterを自動で「Co-authord-by」としてcommit messageに追加するgit hookを作りました🪝
— cangoxina⤴︎💪😇 (@Shitimi_613) 2020年9月4日
モブプロなどでコミットをまとめることが多い人たちにおすすめです。https://t.co/kZlLDPTGYM pic.twitter.com/FBGgw5S2Xc
また、この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:僕にとってはなかなか多い数字なのです。