Git リベース

コミット履歴を整理し、クリーンな直線的履歴を維持するための 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 にリベースしてからマージする。コミットの粒度を保ちつつ、直線的な履歴を維持できる。ただし、開発者全員がリベースの操作に慣れている必要がある。

詳しくは関連書籍を参照。

関連用語