git rebaseとmergeの違い - 使い分けとやってはいけないケース
git merge と git rebase は、どちらも別ブランチの変更を取り込めますが、作られる履歴が異なります。mergeは実際の分岐を残し、rebaseはコミットを新しいベース上へ再生して線形な履歴を作ります。
Merge preserves the real branch topology with a merge commit, while rebase replays commits onto a new base to create linear history. Rebase changes commit hashes, so use it only for private, unshared work.
1. mergeとrebaseを一覧で比較する
| 観点 | merge | rebase |
|---|---|---|
| 既存commit | 書き換えない | 再生成してハッシュ変更 |
| 履歴 | 分岐と統合を保持 | 原則として線形 |
| 共有 | 統合しやすい | 共有済みには不向き |
| 競合 | 統合時に解消 | 各commitで複数回の可能性 |
どちらが常に優れているわけではありません。履歴の見た目だけでなく、そのコミットを他者が利用済みかで判断します。
2. mergeは分岐履歴をそのまま保持する
git switch main
git merge featuremergeは通常、2つの履歴を結ぶマージコミットを作成し、ブランチの分岐と統合をそのまま保持します。既存コミットを書き換えない非破壊的な操作なので、共有ブランチに対して安全です。fast-forward可能な場合はマージコミットが作られないこともあります。
3. rebaseはコミットを新しいベースへ再生する
git switch feature
git rebase main
# merge: A---B---C---M(分岐もMへ接続)
# rebase: A---B---C---D'---E'rebaseはfeature固有のコミットを新しいベースの上へ順番にreplayします。マージコミットを作らずlinearな履歴になりますが、親コミットが変わるためコミットハッシュも変わります。これは履歴の書き換えです。
4. git logで履歴の見た目を比較する
# merge後
* 7f8e9d0 Merge branch 'feature'
|| * e5f6a7b Add validation
| * c3d4e5f Add form
* | b2c3d4e Update dependencies
|/
* a1b2c3d Initial page
# rebase後
* 91a2b3c Add validation
* 80f1a2b Add form
* b2c3d4e Update dependencies
* a1b2c3d Initial pagegit log --graph --oneline --all で確認できます。mergeでもfast-forward可能ならmerge commitは作られません。合流点を必ず残す運用では git merge --no-ff feature を使います。
5. push済みの共有コミットをrebaseしてはいけない
すでにpushされ、他者と共有されているコミットをrebaseしないのが鉄則です。ハッシュが変わるため、他の人のローカルに残る旧履歴とリモートの新履歴が食い違います。rebaseするのは、まだ共有していないローカルまたは自分専用のブランチに限定します。
6. interactive rebaseでPR前に履歴を整理する
git rebase -i HEAD~4
# pick: そのまま使う
# squash: 複数コミットを統合
# reword: メッセージを変更
# edit: 内容を修正
# drop: コミットを削除
# 行の並べ替え: 順序を変更git rebase -i ではsquash、reorder、edit、dropなどが可能です。「修正」「再修正」のようなコミットをまとめ、PR提出前にローカル履歴を整理する用途に向いています。この操作もハッシュを変えるため未共有の履歴で行います。
7. Squash and mergeとの違い
GitHubやGitLabなどのSquash and mergeは、PRまたはMRの複数コミットを1つの新規コミットとしてターゲットへ追加します。線形に見える点は似ていますが、featureブランチ自体をrebaseする操作ではありません。
| 方式 | ターゲットに残るもの |
|---|---|
| Merge commit | 元commitとmerge commit |
| Squash and merge | 変更をまとめた新しい1 commit |
| Rebase and merge | 個々を再生成した線形commit |
レビュー単位を1コミットにしたいチームにはsquashが適しますが、個々のコミットや分岐を監査したい場合はmerge commit方式が向きます。
8. rebase --ontoでコミット範囲を移す
git rebase --onto <newbase> <upstream> <branch>git rebase --onto は、upstreamより後にあるbranchのコミットをnewbaseの上へ移す高度な使い方です。誤ったブランチから派生した作業だけを正しいベースへ付け替える場合などに使います。
9. rebase中のコンフリクトを具体的に解消する
git add <file>
git rebase --continue
git rebase --abort
git rebase --skiprebaseはコミットを1つずつ再生するため、コンフリクトをコミット単位で複数回解消することがあります。解消後は --continue、開始前へ戻すなら --abort を使います。--skip は現在のコミットを飛ばすため、変更が不要な場合だけ使います。詳細はGitコンフリクトの解消方法を参照してください。
<<<<<<< HEAD
const submitLabel = "送信する";
=======
const submitLabel = "保存";
>>>>>>> c3d4e5f (Change submit label)rebase中のHEADは新しいベースと再生済みの状態です。もう一方が現在再生中のコミットです。「oursは自分」と機械的に判断せず、マーカーを削除して完成形へ編集し、diffを確認してadd、continueします。次のコミットでも止まれば繰り返します。
10. git pull --rebaseとブランチ保護
# fetch + rebase
git pull --rebase
# 通常はfetch + merge
git pull通常の git pull は内部的にfetchとmergeを行います。--rebase はローカルの未共有コミットをリモート先端へ再生するため、pull時に余計なマージコミットが増えるのを防げます。
事故防止にはmainへの直接pushとforce pushを禁止し、PR/MR、必須レビュー、必須チェックを設定するブランチ保護が有効です。個人ブランチをrebase後に更新する場合も、許可されている場合だけ git push --force-with-lease を使います。予期しない上書きを減らしますが、履歴書き換え自体を無害にはしません。
よくある間違い
- rebaseはmergeの短縮版と思う: ハッシュを変えます。
- 共有済みmainをrebaseする: 公開済み履歴はmergeで統合します。
- 競合は1回だけと思う: 複数回止まる場合があります。
- --skipを再試行に使う: 現在のcommitを捨てます。
- mergeなら必ずmerge commitができると思う: fast-forwardでは作られません。
- squashをrebaseと同一視する: ターゲットへ1 commitを作ります。
- 競合の片側を機械的に選ぶ: 完成形を確認します。
- leaseならmainにもforce pushできると思う: 保護ルールを優先します。
使い分けの結論
ローカルの個人ブランチの整理・PR提出前のクリーンアップにはrebase、共有・公開されたブランチの統合にはmergeが基本です。履歴の見た目より、他の開発者がすでにそのコミットを基準に作業していないかを優先します。
English summary
Merge preserves topology and existing commits; a fast-forward may omit a merge commit. Rebase replays commits, creates linear history, and changes hashes. Use it only for private commits.
Resolve each conflict to its intended final state, stage it, and continue, or abort to restore the original state. Squash and merge creates one new target commit and is not identical to rebasing the feature branch. Protect main from direct and force pushes.