Git リベース
コミット履歴を整理し、クリーンな直線的履歴を維持するための Git 操作
Git リベースとは
git rebase は、ブランチの基点 (ベース) を別のコミットに移動し、コミット履歴を直線的に書き換える操作である。マージコミットが発生しないため、履歴が読みやすくなる。
Linus Torvalds が Git を開発した当初からある機能だが、「履歴を書き換える」という性質上、使い方を誤ると深刻な問題を引き起こす。Git の中で最も強力かつ危険なコマンドの 1 つだ。
マージとリベースの違い
# マージの場合: マージコミットが作られ、分岐の履歴が残る
main: A---B---C-------M
\ /
feature: D---E---F
# リベースの場合: コミットが再適用され、直線的な履歴になる
main: A---B---C---D'---E'---F'
リベースでは D, E, F のコミットが「新しいコミット」(D', E', F') として再作成される。コミットハッシュが変わる点が重要だ。内容は同じでも、Git 上は別のコミットとして扱われる。
マージとリベースの使い分け
| 観点 | リベース | マージ |
|---|---|---|
| 履歴の見た目 | 直線的でクリーン | 分岐とマージの履歴が残る |
| コンフリクト解決 | コミットごとに 1 つずつ解決 | まとめて 1 回で解決 |
| コミットハッシュ | 変わる (履歴の書き換え) | 変わらない |
| 安全性 | プッシュ済みコミットには危険 | 常に安全 |
| 適するケース | ローカルの未プッシュコミット | 共有ブランチへの統合 |
git bisect |
直線的で二分探索しやすい | マージコミットが邪魔になることがある |
チームによって方針が異なる。Google や Facebook のような大規模リポジトリではリベース + スカッシュが主流だが、OSS プロジェクトではマージコミットを残して貢献の履歴を保存する文化もある。
インタラクティブリベース
git rebase -i は、リベースの真価を発揮する機能だ。コミットの統合 (squash)、分割、メッセージの修正、順序の入れ替え、削除が自由にできる。
git rebase -i HEAD~5
# エディタが開き、直近5コミットの操作を選択できる
pick a1b2c3d ユーザー認証の基盤実装
squash d4e5f6a WIP: ログイン画面
squash 7a8b9c0 fix typo
reword 1d2e3f4 テスト追加
drop 5a6b7c8 デバッグ用console.log
| コマンド | 動作 |
|---|---|
pick |
コミットをそのまま使う |
squash |
前のコミットに統合 (メッセージを編集) |
fixup |
前のコミットに統合 (メッセージは破棄) |
reword |
コミットメッセージだけ変更 |
edit |
コミットを修正 (ファイル変更も可) |
drop |
コミットを削除 |
プルリクエスト前に「WIP」「fix typo」「debug」のようなコミットを整理し、レビュアーが読みやすい履歴にするのが典型的な使い方だ。
黄金ルール - プッシュ済みコミットをリベースしない
リベースの最も重要なルールは、他の開発者と共有済みのコミットをリベースしないことだ。
リベースはコミットハッシュを変更する。他の開発者がそのコミットを基に作業している場合、リベース後に git push --force すると、他の開発者のローカルリポジトリと履歴が不整合になる。最悪の場合、他の開発者の作業が消失する。
# ❌ 絶対にやってはいけない
git rebase main # 共有ブランチのコミットを書き換え
git push --force # 強制プッシュで他の開発者の履歴を破壊
# ✅ 安全な代替
git merge main # マージなら履歴を書き換えない
git push
例外として、自分だけが使っているフィーチャーブランチであれば、git push --force-with-lease (他の人がプッシュしていないことを確認してから強制プッシュ) を使ってリベース結果をプッシュできる。
rebase --onto による高度な操作
--onto オプションを使うと、ブランチの一部のコミットだけを別のブランチに移植できる。
# feature ブランチの途中から分岐した sub-feature を main に直接移植
git rebase --onto main feature sub-feature
これは「feature ブランチがまだマージされていないが、sub-feature の変更だけ先に main に入れたい」というケースで使う。Cherry-pick で 1 コミットずつ拾うより効率的だ。
コンフリクト解決のコツ
リベース中のコンフリクトはコミットごとに発生する。10 コミットをリベースすると、最悪 10 回コンフリクトを解決する必要がある。
# コンフリクト発生時
git rebase main
# CONFLICT: 手動で解決
git add .
git rebase --continue # 次のコミットに進む
# リベースを中断して元に戻す
git rebase --abort
コンフリクトが多発する場合は、git rerere (Reuse Recorded Resolution) を有効にしておくと、一度解決したコンフリクトパターンを Git が記憶し、次回以降は自動解決してくれる。
git config --global rerere.enabled true
チームでの運用パターン
リベース + スカッシュマージ
GitHub の「Squash and merge」ボタンがこのパターン。フィーチャーブランチの全コミットを 1 つにまとめて main にマージする。main の履歴が 1 機能 = 1 コミットになり、非常にクリーンだ。ただし、個々のコミットの粒度が失われるトレードオフがある。
リベース + 通常マージ
フィーチャーブランチを main にリベースしてからマージする。コミットの粒度を保ちつつ、直線的な履歴を維持できる。ただし、開発者全員がリベースの操作に慣れている必要がある。
詳しくは関連書籍を参照。