Pull Request の指摘修正でコミットログが汚れていく問題への対処法の一案

この記事は Akatsuki Advent Calendar 2017 の 1 日目の記事です。

はじめに

この記事では rebase の是非やルールについては議論しません。

開発者が限定されているプライベートリポジトリや、事前に「このブランチはあとで整理する」と通知してあれば、一定の rebase は問題ないという前提の元、話を進めます。

問題

私は普段、GitHub 上で Pull Request ベースで開発しています。
一旦 Pull Request を出したあと、レビューで間違いを指摘されたり、予期しない箇所がテストで落ちたりなどして、追加の修正を行うことは日常的に発生します。

この時、単純な typo など本当に些細なミスは、既存のコミットに修正を入れ込んでしまいたいことも多々あります。

しかしそのようにして push -f すると、レビュアーは追加で修正した箇所がわからなくなってしまいます。

解決

fixup コミットを使ってこの問題を解決します。以下で手順を説明します。

追加の修正を行う

あとから行う修正を、すでに行っているコミットの中に混ぜ込みたい場合、git commit --fixup を使います。例えば直近の Add という単語をコミットメッセージに含むコミットに混ぜ込みたい場合は以下のようにします1

$ git commit --fixup=:/Add

これを実行すると、以下のようなメッセージのコミットが作成されます。

!fixup Add new feature

Add new feature の部分は、指定したコミットのコミットメッセージになります。

レビュー中はこのコミットを push して行きます。こうすることで新たな変更が明確になるので、レビュアーは新しい変更だけをレビューできます。

コミットを整理する

さて、一通りの修正が終わって、マージできる状態になりました。ここで積んでいた fixup コミットを通常のコミットに混ぜ込みます。

# 元ブランチが master の場合
$ git -c core.editor=true rebase -i --autosquash $(git merge-base master @)

これで !fixup コミットが、--fixup= で指定したコミットに入れ込まれます。 2

問題なければリモートブランチに反映します。

$ git push -f

何が起きているのか

何が起きているのかを確認するために、いくつかオプションを取り除いて実行してみます。

$ git rebase -i $(git merge-base master @)

ここで $(git merge-base master @) は単にベースになるコミットを指定しています。代わりに @~10 のような数の指定や、直接 hash を指定しても構いません。

さて、上記のコマンドを実行すると、以下のような内容でテキストエディタが起動するはずです。rebase -i を使ったことがある人ならば見慣れた内容でしょう。

pick 02d6268 Add new feature
pick 0cec502 Improve new feature
pick 89a3f4b fixup! Add new feature

既存のログ通り、一番最後に fixup! のコミットが積まれています。

ここで一旦 rebase をキャンセルして、今度は --autosquash を付けて実行してみます。

$ git rebase -i --autosquash $(git merge-base master @)

すると今度は以下のような内容になります。

pick 02d6268 Add new feature
fixup 89a3f4b fixup! Add new feature
pick 0cec502 Improve new feature

fixup! のコミットが Add new feature の直後に移動し、さらに操作が pick から fixup に変わっています。

これが autosquash の機能です。fixup! で始まるコミットがあると、その後ろにあるメッセージのコミットを探してその下にコミットを移動し、操作を fixup に変更した状態でテキストエディタが開かれます。

あとはこれを何も変更せずに終了すれば、fixup が適用された rebase が完了します。そのために以下のオプションを追加します。

$ git -c core.editor=true rebase -i --autosquash $(git merge-base master @)

-c core.editor=true というオプションを渡しています。これは git rebase -i で起動するテキストエディタに true コマンドを使うと言う意味です。ご存知の通り、true コマンドは何もせずに終了コード 0 で終了するコマンドです。つまり git rebase -i で開かれる TODO リストに対して何も変更を加えずに git rebase -i を実行しているということです3

ちなみにここまでずっと fixup のみを言及してきましたが、autosquash という名前からもわかるように squash も同様に可能です。--squash={commit} を使うと !squash ... コミットが生成され、rebase 時に自動的に squash になります。

おわりに

fixup を用いたブランチ整理の機能は以前からあり、探せば解説している記事もいくつか見付かります。この記事では、これを Pull Request のレビューの際に活用する提案をしました。

最初にも書いた通り、一度公開されたブランチ上でこれを行うのは賛否があるでしょう。事前にチームメンバーに確認を取っておくとスムーズです。

Git のログは日記帳ではありません。実際に起きた事細かなことを記録するのではなく、あとから見てわかりやすいログを残せるとよいですね。


  1. :/Add の部分は、単にコミットを指定しています。直接 hash 値や @~3 のような指定も可能です。 

  2. 混ぜ込むコミットを間違えたりすると、場合によってはコンフリクトが起きる場合もあります。 

  3. 例えば true の代わりにシェルの組み込みコマンドの : などでも同様の結果が得られます。