Git Interactive Rebase:本番環境へのデプロイ前に「ゴミコミット」をきれいに整理する方法

Git tutorial - IT technology blog
Git tutorial - IT technology blog

深夜2時の悪夢と意味のないコミットメッセージ

自分のgit logを振り返って、恥ずかしくなったことはありませんか?深夜2時、ターミナル画面を見つめながら、重要な機能をmainブランチにマージしようとしていた時のことです。git log --onelineと入力した私は、思わず手が止まりました。目の前には、次のようなカオスが広がっていたのです:

a7b2c3d typo修正
9e8f7g6 更新
5h4i3j2 バグ修正
1k2l3m4 再度バグ修正
0n9o8p7 今度こそ動くと誓います
6q5r4s3 決済ロジックを追加

この状態のままPull Request(PR)を作成すれば、翌朝リードエンジニアに「お説教」されるのは間違いありません。たった一つのタイポを直すのに4つもコミットを残すなんて、プロとしてどうなのでしょうか?これではGitの履歴が非常に見づらくなってしまいます。将来、git bisectを使ってバグを追跡する必要が出てきたとき、こうした「ゴミコミット」は大きな障害になります。そこで救世主として登場するのが、Git interactive rebaseです。

Git interactive rebaseとは一体何か?

基本的に、git rebaseとは自分のコミットを新しいベース(基点)の上に置き直す作業です。そこに-i(interactive)フラグを付けると、Gitはエディタ画面を開き、過去のコミット一つひとつに対して直接介入できるようになります。

このツールを使えば、以下のような「神がかった」操作が可能になります:

  • Squash: 10個の細かなバラバラのコミットを、一つの大きなコミットにまとめます。
  • Reword: 「asdfgh」のような適当なメッセージを、意味の通る内容に書き換えます。
  • Drop: テスト用の汚いコードが含まれたコミットを完全に削除します。
  • Reorder: コミットの順番を入れ替えて、ロジックの流れを分かりやすくします。

これは、映画の編集作業に似ています。不要なシーンをカットし、観客が最も理解しやすいように場面を並べ替えるのです。

実践:ゴミコミットの掃除を開始する

まず、何個前までのコミットを遡りたいかを決めます。例えば、直近5つのコミットを整理したい場合は次のように入力します:

git rebase -i HEAD~5

すると即座に、デフォルトのエディタ(VimやNano)が開き、コミットのリストが表示されます。ここで注意が必要なのは、順番がgit logとは逆である点です。一番上が最も古いコミット、一番下が最新のコミットになります。

pick 6q5r4s3 決済ロジックを追加
pick 0n9o8p7 今度こそ動くと誓います
pick 1k2l3m4 再度バグ修正
pick 5h4i3j2 バグ修正
pick 9e8f7g6 更新
pick a7b2c3d typo修正

1. コミットをまとめる(Squash & Fixup)

これは私が最も頻繁に使う機能です。バラバラな4つのバグ修正コミットを残す代わりに、それらをメインのコミットに統合します。子コミットのpickという文字をf(fixup)に書き換えてください。fixupは、そのコミットのメッセージを破棄し、前のコミットに内容を統合します。

pick 6q5r4s3 決済ロジックを追加
f 0n9o8p7 今度こそ動くと誓います
f 1k2l3m4 再度バグ修正
f 5h4i3j2 バグ修正
f 9e8f7g6 更新
f a7b2c3d typo修正

2. メッセージを修正する(Reword)

うっかり「bug fix」という名前にしてしまったけれど、「Fix: 画像アップロード時のメモリリークを修正」のように具体的に書き換えたい場合は、pickreword(またはr)に変更します。ファイルを保存した後、Gitが新しいメッセージを入力するためのウィンドウを別に開いてくれます。

3. コミットを削除する(Drop)

テストコードや個人的な設定ファイルが含まれたコミットを見つけた場合は、pickdrop(またはd)に変更します。あるいは、単にその行を削除するだけでも構いません。そのコミットは、最初から存在しなかったかのように消え去ります。

衝突(コンフリクト)の解決と完了

編集が終わったら、ファイルを保存して終了します(Vimの場合は:wq)。すべてがスムーズにいけば、成功のメッセージが表示されます。しかし、現実はそう甘くないこともあります。時折、衝突(conflict)が発生します

慌てる必要はありません。Gitはコードを修正するために一時停止してくれます。修正が終わったら、次のように入力するだけです:

git add .
git rebase --continue

もし収拾がつかなくなったと感じたら、次の「脱出コマンド」で元の状態に戻ることができます:

git rebase --abort

血の教訓:Force Pushを甘く見てはいけない

ローカルでのリベースが終わると、コミット履歴はリモートサーバーのものとは全く別物になっています。この時、通常のgit pushは拒否されます。そのため、--forceを使う必要があります。

私はかつて、force pushするブランチを間違えて午前中の作業をすべて台無しにしたことがあります。個人ブランチにプッシュするつもりが、誤ってチームのdevelopブランチにプッシュしてしまったのです。結果として、同僚3人のコードを上書きして消し去ってしまいました。冷や汗をかきながらgit reflogを2時間使い倒し、なんとか復元した苦い経験があります。

私からのアドバイスは、--force-with-leaseを使うことです。これは、自分がリベースしている間に誰か他の人がリモートにプッシュしていないかを確認してくれます。もし更新があればプッシュを止めてくれるので、他人の作業を上書きしてしまうリスクを減らせます。

git push origin feature/payment --force-with-lease

Interactive Rebaseを避けるべきケース

非常に強力なツールですが、一つだけ「黄金律」があります。それは、共有ブランチにプッシュ済みのコミットをリベースしてはいけないということです。

リベースは本質的に新しいコミットIDを生成します。もしmainブランチの履歴を書き換えてしまうと、他のメンバーがgit pullした際に地獄を見ることになります。あくまで、マージリクエスト(PR)を出す前の、自分専用のブランチでのみ使用するようにしましょう。

最後に

Git interactive rebaseは、コードレビュアーに対して自分のプロ意識を示す一つの方法です。クリーンなコミット履歴は、チームが「何をしたか」「なぜそうしたか」を素早く理解する助けになります。将来のコードメンテナンスも、これによって格段に楽になります。

次回、コードを本番環境に持っていく前に、5分だけ時間を取って整理してみてください。信じてください、同僚たちは心の中であなたに感謝するはずです!

Share: