かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

git コミットログを綺麗にしたい。fixupとsquash

チーム開発をしているプロダクトで タイポを修正しただけとか、コミットログが本当にただの履歴になっているままPRをだしたりしてmasterにマージされてしまうとmasterブラントが本質ではないコミットというノイズが混ざり、後から遡って見づらくなったりしてしまいます。

PRの際にブランチのコミットを整理するルールや方法はチームやプロダクトによって違うと思いますが、 PRを出してコードレビューで指摘を受けた箇所を直し、最終的に修正部分のコミットを元のコミットに結合してコミットログを修正する方法を例にメモ

e.g.

例えば次のようなジャパリバスを直したPRのコミットログがあるとします

$ git log --oneline
ca284e8 (HEAD -> master) しうんてんをしたよ
30c99a8 バス的なものをボスとつないだよ
bb971d8 いすを取り付けたよ
6d8ee79 ハンドルを取り付けたよ
4917aae でんちを取り付けたよ
4d5b197 でんちを見つけてきたよ
332eb07 タイヤをさがしたよ
cdbe6ff バス的なものの足りないパーツを調べるよ

4917aaeで"でんち"を取り付けたつもりだったけどコードを繋ぎ忘れてたので修正したとします。
そのまま普通に修正コミットをすると

XXXXXX (HEAD -> master) でんちのコードつけわすれてた
ca284e8 しうんてんをしたよ
30c99a8 バス的なものをボスとつないだよ
bb971d8 いすを取り付けたよ
6d8ee79 ハンドルを取り付けたよ
4917aae でんちを取り付けたよ
4d5b197 でんちを見つけてきたよ
332eb07 タイヤをさがしたよ
cdbe6ff バス的なものの足りないパーツを調べるよ

みたいな感じになってしまうので、この修正は4917aae でんちを取り付けたよと一緒にしたい。

--fixup ・rebaseで修正のコミットをまとめる

fixupコミット(修正コミット)を作成する

git commit --fixup <target hash>

修正のコミットする際に、--fixup--fixup= に続けて一緒にしてしまいたいターゲットコミットのハッシュを指定します。
今回の例では4917aae でんちを取り付けたよと一緒にしたいので次のような感じでコミット

git commit --fixup 4917aae

コミットメッセージの入力はなく次のようなコミットが作成されました

(HEAD -> master) fixup! でんちを取り付けたよ

rebase -i --autosquash でコミットをまとめる

--autosquashオプションを付けてrebaseすると先のfixup!な修正コミットが自動的に合体させたいコミットと統合される。rebaseする時は修正するコミットの1つ前のコミットをターゲットに指定するので、今回の場合は4917aae でんちを取り付けたよにコミットを統合するので1つ前の4d5b197 でんちを見つけてきたよを指定する

$ git rebase -i --autosquash 4d5b197

エディタが開き、fixup!コミットがターゲットの下に自動的に移動している事を確認

pick 4917aae でんちを取り付けたよ
fixup 50689a2 fixup! でんちを取り付けたよ
pick 6d8ee79 ハンドルを取り付けたよ
pick bb971d8 いすを取り付けたよ
pick 30c99a8 バス的なものをボスとつないだよ
pick ca284e8 しうんてんをしたよ

:wqでファイルを保存して特に問題がなければrebaseが完了します。
コミットログを確認すると

$ git log --onelin
68155df (HEAD -> master) しうんてんをしたよ
c3fa632 バス的なものをボスとつないだよ
a81f850 いすを取り付けたよ
5e6545e ハンドルを取り付けたよ
9bc5ea3 でんちを取り付けたよ
4d5b197 でんちを見つけてきたよ
332eb07 タイヤをさがしたよ
cdbe6ff バス的なものの足りないパーツを調べるよ

fixup!コミットが消え、変更内容がターゲットにしていた でんちを取り付けたよ にまとめられました。

既にコミットされている場合

既に修正コミットがコミットされてしまっている場合は

$ git log --oneline
5eb844c (HEAD -> master) 修正 でんちコードつなぎわすれ
68155df しうんてんをしたよ
c3fa632 バス的なものをボスとつないだよ
a81f850 いすを取り付けたよ
5e6545e ハンドルを取り付けたよ
9bc5ea3 でんちを取り付けたよ
4d5b197 でんちを見つけてきたよ
332eb07 タイヤをさがしたよ
cdbe6ff バス的なものの足りないパーツを調べるよ

rebase -iで修正コミットをfixupコミットに変更してコミットログをまとめます。
まとめたいターゲットの1つまえのコミットを指定して

$ git rebase -i 4d5b197
  1. エディタが開くので修正するコミットのpickfixup (f)に変更
    pick 9bc5ea3 でんちを取り付けたよ
    pick 5e6545e ハンドルを取り付けたよ
    pick a81f850 いすを取り付けたよ
    pick c3fa632 バス的なものをボスとつないだよ
    pick 68155df しうんてんをしたよ
    fixup 5eb844c 修正 コードつなぎわすれ
    
  2. fixupにしたコミットの行を合体させたいコミットの下に移動 ( Viならddで1行カット、pでペースト )
    pick 9bc5ea3 でんちを取り付けたよ
    fixup 5eb844c 修正 コードつなぎわすれ
    pick 5e6545e ハンドルを取り付けたよ
    pick a81f850 いすを取り付けたよ
    pick c3fa632 バス的なものをボスとつないだよ
    pick 68155df しうんてんをしたよ
    
  3. ファイルを保存。rebaseが実行されコミットがマージされる

ログを確認するとfixupコミットの変更内容がfixupにしたコミットを移動させた上のコミットにマージされ、fixupにしたコミットメッセージは消え、元のコミットメッセージだけが残った状態になりコミットをまとめることが出来ました。

fixup と squash の違い

fixupに似たコミットをまとめられるものにsquashがあります。
rebase時に開くエディタの説明を引用すると、違いはコミットメッセージを残すかどうかということのようです。

  • squash (s): use commit, but meld into previous commit ... コミットメッセージを残し直前のコミットとまとめる
  • fixup (f): like "squash", but discard this commit's log message ... コミットメッセージを削除して直前のコミットとまとめる

e.g.

$ git log --oneline
a19959c (HEAD -> master) 修正 でんちの充電
3609605 しうんてんをしたよ
4a12fdd バス的なものをボスとつないだよ
24961f5 いすを取り付けたよ
6aa20fb ハンドルを取り付けたよ
56de548 でんちを取り付けたよ
4d5b197 でんちを見つけてきたよ
332eb07 タイヤをさがしたよ
cdbe6ff バス的なものの足りないパーツを調べるよ

a19959cのコミットを56de548のコミットにまとめたいと思います。
56de548の1つ前のコミットを指定してrebase

$ git rebase -i 4d5b197

エディタが開くのでa19959cpicksquash (s)に変更して、56de548の下に移動

pick 56de548 でんちを取り付けたよ
s a19959c 修正 でんちの充電
pick 6aa20fb ハンドルを取り付けたよ
pick 24961f5 いすを取り付けたよ
pick 4a12fdd バス的なものをボスとつないだよ
pick 3609605 しうんてんをしたよ

ファイルを保存してrebaseを完了させるとsquashにしたコミットが消えて、修正内容が56de548のコミットに合算されています。
コミットメッセージは

$ git log
...
commit 4d39d57aca4c7ae1a3ba48cc426b506e022db442
Author: KiKiKi 
Date:   Mon Feb 25 15:05:17 2019 +0900
    でんちを取り付けたよ

    修正 でんちの充電
...

fixupと異なり、元のコミットメッセージが合体させたコミットメッセージに追加され残っています。

squashでコミットする場合

fixupと同じようにsquash--squashオプションでコミットすることが出来ます。

$ git log --oneline
3609605 しうんてんをしたよ
4a12fdd バス的なものをボスとつないだよ
24961f5 いすを取り付けたよ
6aa20fb ハンドルを取り付けたよ
56de548 でんちを取り付けたよ
4d5b197 でんちを見つけてきたよ
332eb07 タイヤをさがしたよ
cdbe6ff バス的なものの足りないパーツを調べるよ
$ git commit --squash <target hash>

エディタが開くのでそのまま保存すると、squash! <ターゲットのコミットメッセージ>というコミットが作成されます。

3e22c20 (HEAD -> master) squash! でんちを取り付けたよ

--autosquashオプションを付けてrebaseの実行

$ git rebase -i --autosquash <まとめるコミットの1つ前のコミット>

エディタが開きsquash!のコミットが自動的にターゲットのコミットの下に移動しています

pick 56de548 でんちを取り付けたよ
squash 3e22c20 squash! でんちを取り付けたよ
pick 6aa20fb ハンドルを取り付けたよ
pick 24961f5 いすを取り付けたよ
pick 4a12fdd バス的なものをボスとつないだよ
pick 3609605 しうんてんをしたよ

ファイルを保存するとrebaseが実行される前に、squashで統合されるコミットのメッセージを修正するエディタが起動します。

 This is a combination of 2 commits.
# This is the 1st commit message:

でんちを取り付けたよ

# This is the commit message #2:

squash! でんちを取り付けたよ

squash!で始まるメッセージ部分を修正内容に変更して、でファイルを保存すればrebaseが実行され、先程変更したメッセージのコミットに変更内容が統合されました。

--squashでのrebaseはコミットが移動している所でメッセージをへんこうしても何故か、メッセージの修正エディタが開いて再度メッセージの編集を強いられてしまってメンドーだったので、コミットしてしまってたものをrebaseで手動で移動させるほうが楽な感じでした。

rebase時に自動的に--autosquashを付ける

--autosquashオプションとして長かったり、付けわすれてあれ?ってなったりしがちなので、 rebase -iの時に自動的に--autosquashオプションが付くようにしておくと楽です。

$ git config --global --add rebase.autosquash true

まとめと感想

--squashオプションでのコミットでの挙動的にgit rebase -i --autosquashfixup!squash!キーワードから始まるコミットを、キーワード以降の文字列が一致するコミットの下に移動させているだけなんじゃないかなという印象でした。
fixupなら修正コミットのメッセージが残らないので、--fixupオプションでコミットしてしまいrebaseするのが簡単で良さそうです。コミットメッセージを残すsquashなら残すべきメッセージでコミットしてしまってrebaseの時にpicksに変更し、統合したいコミットの下に移動させる方が楽かなという肌感でした。

git rebaseでコミットログを綺麗にした場合pushするには-fでforce pushしなければならなくなるので、チーム開発のPRなどの修正でも場合複数人がそのPRのブランチに関わっているとか、ステージングにそのブランチpullしたとかあると、rebaseしてのforce pushがあると結構メンドーな事にもなりかねないので、PR出した人がmasterにマージする前にrebaseするとかチームでルールの認識合わせが必要だなーと思いました。

個人的にはミス含め過去の履歴修正主義には否定的だったのですが、チーム開発でコミットの粒度が細かくコミット数の多いプロジェクトなら確かに遡って調べやすくなるように、ノイズになるコミットはrebaseでまとめてしまい読みやすいコミットログを作るってのは確かにアリだなと思いました。個人開発だとコミット量もそんなに増えないし、自分のやったことのログなのであまり他人が見てもわかりやすいログって今まで意識がなかったなーという気づきがありました。


[参考]