GitHub

git rebaseとは?使い方・mergeとの違い・コンフリクト解決とやり直しを解説

git rebaseは、あるブランチのコミットを別のコミットの上に付け替えて、履歴を一直線に整える操作です。マージコミットを残すgit mergeと違い、分岐した経緯を消して直線的な履歴を作れる一方、コミットのハッシュIDが書き換わるため、共有済みのブランチに使うと事故につながります。この記事では、rebaseの仕組みと基本的な使い方、mergeとの使い分け、-iによるコミット整理、コンフリクト解決、--force-with-leaseでの安全なpush、そしてreflogを使ったやり直しまでを、実際のコマンドで解説します。

まとめ:git rebaseの要点

  • 仕組み:分岐元を移動させてコミットを載せ直す。元のコミットは破棄され、内容が同じでも新しいハッシュIDのコミットに置き換わる
  • mergeとの使い分け:自分の手元(未push)ブランチを最新のmainに追従させる・履歴を整えるならrebase。共有済みの統合はmerge。
  • ゴールデンルール他人と共有済み(push済み)のブランチはrebaseしない。履歴が食い違い、共同作業者を巻き込む。
  • やり直し:作業中はgit rebase --abort、完了後の取り消しはgit refloggit reset --hardで元のコミットに戻せる。
  • pushはlease付きで:rebase後の強制pushは--forceではなく--force-with-leaseを使い、他人の更新を上書きしないようにする。

git rebaseとは:履歴を付け替える仕組み

rebaseは「rebase=土台(base)を置き換える」という名前のとおり、ブランチが分岐した基点を別のコミットに移し替える操作です。featureブランチをmainにrebaseすると、Gitはfeatureだけにあるコミットを一度退避し、最新のmainの先端から順番に適用し直します。

ここで重要なのは、適用し直されたコミットは元とは別のコミットだという点です。変更内容が同一でも、親コミットが変わるためハッシュIDが変わります。元のコミットは参照されなくなり、やがてガベージコレクションで消えます。これが「履歴の書き換え」と呼ばれる理由であり、後述する共有ブランチでの禁止ルールの根拠です。

git rebaseとgit mergeの違いと使い分け

mergeは二つのブランチを統合するマージコミットを新たに作り、分岐と合流の経緯をそのまま残します。rebaseはマージコミットを作らず、履歴を一直線に並べ替えます。どちらが正しいということはなく、目的で選びます。

観点 git merge git rebase
履歴の形 分岐と合流が残る 一直線になる
マージコミット 作る 作らない
コミットID 変わらない 書き換わる
共有済みブランチ 安全 原則使わない
向く場面 共有ブランチの統合・経緯を残したい 手元ブランチのmain追従・履歴整理

実務では「自分の手元にある未pushのブランチはrebaseで整える/他人と共有した統合はmergeで残す」がひとつの指針になります。プルリクエストのマージ方式に迷う場合、コミットの粒度を保ちたいならmerge、履歴を追いやすくしたいならsquash mergeやrebaseを選びます。git pullをgit pull --rebaseにすると、リモートの更新を取り込むときにマージコミットを作らず手元のコミットを載せ直せます(git config --global pull.rebase trueで既定化)。

git rebaseの基本的な使い方

もっとも基本的な形は、作業ブランチを最新の統合先に載せ直す使い方です。

git switch feature
git rebase main

これでfeatureのコミットが最新のmainの先端に付け替わります。分岐の一部だけを別の基点へ移したいときは、--ontoで「どこから・どこへ」を明示します。

git rebase --onto main develop feature

これは「developから分岐したfeatureを、mainの上に載せ替える」指定です。stackした複数ブランチをまとめて追従させたいときは、Git 2.38(2022年10月)で追加された--update-refsが使えます。rebase対象の途中を指すブランチも自動で前に進めてくれるため、依存し合うブランチを一括で更新できます。

git rebase --update-refs main

インタラクティブリベース(-i):コミットの統合・並べ替え・削除

git rebase -i(interactive)は、直近のコミット列をエディタで開き、1コミットずつ操作を指定して履歴を編集する機能です。直近3コミットを対象にするなら次のようにします。

git rebase -i HEAD~3

開いたエディタで各コミット行の先頭コマンドを書き換えて、統合・要約・削除・並べ替えを指示します。主なコマンドは次のとおりです。

コマンド 動作
pick そのコミットをそのまま採用(行を並べ替えると順序も変わる)
reword コミットメッセージだけ書き直す
squash 直前のコミットに統合し、メッセージを両方まとめて編集
fixup 直前に統合するが、メッセージは直前のものを使う(自分のメッセージは破棄)
drop そのコミットを履歴から削除
edit その地点で停止し、変更の修正やコミット分割を行う

細かい修正コミットを後からまとめる作業は、--autosquashで自動化できます。修正時にgit commit --fixup=<対象コミット>でコミットしておくと、rebase時に対象の直後へ自動で並べ替えられます。

git commit --fixup=<対象コミット>
git rebase -i --autosquash main

rebase中のコンフリクト解決(–continue / –abort)

載せ直す途中で、同じ箇所を別々に変更しているとコンフリクトが起きてrebaseが停止します。コンフリクトは1コミットずつ順番に発生するため、その都度解決して先へ進めます。手順は決まっています。

git add <解決したファイル>
git rebase --continue

そのコミットの適用自体をやめて飛ばすならgit rebase --skip、途中でやめてrebase前の状態に完全に戻すならgit rebase --abortを使います。abortは元のブランチ位置に巻き戻すので、迷ったらまずabortして安全な状態に戻すのが確実です。

同じコンフリクトを何度も解決させられる場合は、rerere(reuse recorded resolution)を有効にすると、一度解決した内容を記録して次回以降に自動適用します。

git config --global rerere.enabled true

rebase後のpushと安全に扱う原則

rebaseするとコミットIDが書き換わるため、すでにpush済みのブランチは通常のpushがrejected(拒否)されます。リモートの履歴と食い違うためで、これを通すには強制pushが必要になります。ただし素の--forceは、自分がrebaseしている間に他人が同じブランチへ加えた更新まで問答無用で消してしまいます。必ず--force-with-leaseを使ってください。

git push --force-with-lease origin feature

--force-with-leaseは「自分が最後に見たリモートの状態から変わっていなければ上書きする」という条件付きの強制pushで、他人の更新が入っていれば拒否されます。そのうえで守るべき原則が「共有済み(他人がpull済み)のブランチはrebaseしない」というゴールデンルールです。mainや共同作業中のブランチをrebaseすると、全員の手元履歴と食い違い、収拾がつかなくなります。rebaseは自分だけが触っている手元ブランチに限定するのが安全です。

rebaseのやり直し・失敗からの復旧(reflog)

rebaseを完了した後で「やっぱり元に戻したい」というときも、コミットはすぐには消えません。git reflogにはHEADが指してきた履歴が残っており、rebase直前のコミットに戻せます。

git reflog
git reset --hard HEAD@{5}

reflogrebase開始前の行を探し、その位置(例ではHEAD@{5})にreset --hardすれば、rebase前の状態が丸ごと復元されます。コンフリクト解決を間違えた、コミットを消しすぎた、といった失敗もこの手順で取り戻せます。作業中の中断であれば前述のgit rebase --abortが最短です。reflogという安全網があるため、rebaseは「壊しても戻せる」と理解しておくと落ち着いて扱えます(ただしreflogの保持期間はデフォルト90日で、rebase後のようにブランチ先端から到達できなくなったコミットの参照はgc.reflogExpireUnreachableの既定30日で先に整理されるため、やり直すなら早めに行います)。

よくある質問

Q. git rebaseとgit merge、どちらを使うべきですか?
手元の未pushブランチを最新mainに追従させる・履歴を整えるならrebase、共有済みブランチの統合や分岐の経緯を残したいならmergeです。共有済みブランチにrebaseは使いません。

Q. rebaseを取り消すには?
作業中ならgit rebase --abortで開始前に戻ります。完了後はgit reflogでrebase前のコミットを探し、git reset --hard HEAD@{n}で復元します。

Q. squashとfixupの違いは?
どちらも直前のコミットへ統合しますが、squashは両方のメッセージをまとめて編集でき、fixupは自分のメッセージを破棄して直前のメッセージをそのまま使います。

Q. rebase後にpushがrejectedされるのはなぜ?
rebaseでコミットIDが変わり、リモートの履歴と食い違うためです。git push --force-with-leaseで条件付きの強制pushをします(素の--forceは避ける)。

Q. git pull –rebaseとは?
リモートの更新を取り込む際にマージコミットを作らず、手元のコミットを取り込み後の先端に載せ直すpullです。履歴が一直線に保たれます。

関連記事

資料請求

RELATED POSTS 関連記事