Git rebaseとmergeの違いを完全理解|きれいな履歴を保つ実践ガイド

kento_morota 13分で読めます

「rebaseとmergeの違いがわからない」「rebaseは危険と聞くが、本当に使わない方がいいのか」――Git操作の中でも、rebaseとmergeの使い分けは多くの開発者が悩むポイントです。

この記事では、rebaseとmergeの仕組みの違いを根本から理解し、状況に応じた使い分けの判断基準を、実践的なコマンド例とともに解説します。

mergeの仕組みを理解する

まず、mergeの基本的な動作を正確に理解しましょう。mergeは「2つのブランチの変更を統合する」操作で、2つの方式があります。

Fast-Forward Merge

分岐元のブランチに新しいコミットがない場合、ブランチポインタを単純に前に進めるだけで統合が完了します。これをFast-Forward Mergeと呼びます。

# featureブランチがmainの先端から分岐し、mainに変更がない場合
# main:    A --- B
# feature:         C --- D

git checkout main
git merge feature

# 結果(Fast-Forward):
# main:    A --- B --- C --- D
# マージコミットは作成されない
# Fast-Forwardを強制的に禁止してマージコミットを作る
git merge --no-ff feature

# 結果:
# main:    A --- B --------- M
#                  \         /
# feature:          C --- D

3-Way Merge

分岐元のブランチにも新しいコミットがある場合、Gitは共通の祖先を基準に3つのスナップショットを比較し、マージコミットを作成します。

# mainにもfeatureにも変更がある場合
# main:    A --- B --- E --- F
# feature:         \
#                   C --- D

git checkout main
git merge feature

# 結果(3-Way Merge):
# main:    A --- B --- E --- F --- M
#                  \              /
# feature:          C --- D ----

マージコミット(M)には親コミットが2つあり、分岐と統合の履歴が保存されます。

rebaseの仕組みを理解する

rebaseは「コミットを別のベース(基点)の上に移し替える」操作です。mergeとは根本的にアプローチが異なります。

rebaseの動作

# rebase前
# main:    A --- B --- E --- F
# feature:         \
#                   C --- D

git checkout feature
git rebase main

# rebase後
# main:    A --- B --- E --- F
# feature:                    \
#                              C' --- D'
# C'とD'は新しいコミット(ハッシュが変わる)

rebaseの重要なポイントは以下の通りです。

  • コミットが「移動」するのではなく「再作成」される:C, Dのコミットハッシュが変わる
  • 分岐の履歴が消える:直線的な履歴になる
  • コンフリクトはコミットごとに解消:mergeの1回とは異なり、各コミットで解消が必要

rebaseの基本コマンド

# featureブランチをmainの最新にrebase
git checkout feature
git rebase main

# コンフリクトが発生した場合
# 1. コンフリクトを解消
# 2. ファイルをステージ
git add .
# 3. rebaseを続行
git rebase --continue

# rebaseを中断して元に戻す
git rebase --abort

# rebaseをスキップ(現在のコミットを破棄)
git rebase --skip

rebaseとmergeの違いを比較する

両者の違いを具体的な観点で比較します。

履歴の形

# merge: 分岐と統合の履歴が残る
# git log --graph --oneline
# *   abc1234 Merge branch 'feature'
# |\
# | * def5678 feat: 機能Bを実装
# | * ghi9012 feat: 機能Aを実装
# * | jkl3456 fix: バグ修正
# |/
# * mno7890 initial commit

# rebase: 直線的な履歴になる
# git log --graph --oneline
# * def5678 feat: 機能Bを実装
# * ghi9012 feat: 機能Aを実装
# * jkl3456 fix: バグ修正
# * mno7890 initial commit

コンフリクト解消の違い

# merge: 1回のマージで全コンフリクトを解消
git merge feature
# → コンフリクト発生 → 全ファイルを一度に解消 → コミット

# rebase: コミットごとにコンフリクトを解消
git rebase main
# → コミットC'でコンフリクト → 解消 → git rebase --continue
# → コミットD'でコンフリクト → 解消 → git rebase --continue
# → 完了

安全性

mergeは既存のコミット履歴を変更しないため安全です。rebaseはコミットを再作成するため、他の開発者が同じブランチを参照している場合に問題が発生します。

# 危険: プッシュ済みのブランチをrebaseしてforce push
git checkout feature
git rebase main
git push --force origin feature
# → 他の開発者のローカルブランチと不整合が生じる

# 安全: --force-with-leaseを使う
git push --force-with-lease origin feature
# → リモートが予期しない変更をされていた場合は拒否される

使い分けの判断基準

rebaseとmergeの使い分けには、チームで合意したルールが重要です。以下は広く採用されている判断基準です。

rebaseを使うべき場面

# 1. ローカルのfeatureブランチをmainの最新に追従させる
git checkout feature
git rebase main
# → プッシュ前のローカルブランチなので安全

# 2. プルリクエスト前にコミットを整理する
git rebase -i HEAD~3
# → レビュアーが読みやすい履歴にする

# 3. git pullでリモートの変更を取り込む
git pull --rebase origin main
# → 不要なマージコミットを避ける

mergeを使うべき場面

# 1. featureブランチをmainに統合する(プルリクエスト経由)
git checkout main
git merge --no-ff feature
# → 機能の開発履歴をマージコミットとして記録

# 2. 共有ブランチの統合
# develop → mainなど、チーム全体で使うブランチの統合

# 3. リリースブランチの統合
git checkout main
git merge --no-ff release/1.0.0

黄金ルール:共有ブランチではrebaseしない

rebaseの最も重要なルールは、他の開発者と共有しているブランチではrebaseしないことです。

# やってはいけない
git checkout main
git rebase feature  # mainをrebase → 全員に影響

# やってよい
git checkout feature  # 自分だけが使うブランチ
git rebase main       # ローカルでrebase → 安全

インタラクティブrebaseで履歴を整理する

インタラクティブrebase(git rebase -i)は、コミット履歴を柔軟に編集できる強力な機能です。プルリクエスト前のコミット整理に最適です。

基本操作

# 直近3つのコミットを編集対象にする
git rebase -i HEAD~3

# エディタが開き、以下のような表示が出る:
# pick abc1234 feat: 検索フォームを追加
# pick def5678 fix: 検索フォームのtypo修正
# pick ghi9012 feat: 検索結果の表示を実装

# 操作コマンド:
# pick   = コミットをそのまま使用
# reword = コミットメッセージを変更
# edit   = コミットを修正
# squash = 前のコミットに統合(メッセージも統合)
# fixup  = 前のコミットに統合(メッセージは破棄)
# drop   = コミットを削除

コミットのsquash(統合)

# typo修正のコミットを前のコミットに統合
# エディタで以下のように変更:
# pick abc1234 feat: 検索フォームを追加
# fixup def5678 fix: 検索フォームのtypo修正  ← fixupに変更
# pick ghi9012 feat: 検索結果の表示を実装

# 結果: 2つのコミットになる
# * abc1234 feat: 検索フォームを追加(typo修正も含む)
# * ghi9012 feat: 検索結果の表示を実装

コミットメッセージの修正

# rewordでコミットメッセージを変更
# pick abc1234 feat: 検索フォームを追加
# reword ghi9012 feat: 検索結果の表示を実装  ← rewordに変更

# 保存すると、メッセージ編集用のエディタが開く

コミットの順序変更

# エディタ内で行を入れ替えるだけ
# pick ghi9012 feat: 検索結果の表示を実装  ← 上に移動
# pick abc1234 feat: 検索フォームを追加    ← 下に移動

実践:プルリクエスト前のコミット整理ワークフロー

実際の開発で、featureブランチの履歴を整理してプルリクエストを出すまでのワークフローを紹介します。

開発中の荒い履歴

# 開発中のコミット履歴(よくある状態)
git log --oneline
# abc1234 WIP: 検索機能の途中実装
# def5678 fix: ビルドエラーを修正
# ghi9012 feat: 検索APIを実装
# jkl3456 fix: typo
# mno7890 feat: 検索フォームを追加
# pqr1234 WIP: 初期実装

コミットを整理する

# 1. mainの最新にrebase
git fetch origin
git rebase origin/main

# 2. インタラクティブrebaseでコミットを整理
git rebase -i origin/main

# エディタで以下のように編集:
# pick pqr1234 WIP: 初期実装
# squash mno7890 feat: 検索フォームを追加
# squash jkl3456 fix: typo
# squash ghi9012 feat: 検索APIを実装
# squash def5678 fix: ビルドエラーを修正
# squash abc1234 WIP: 検索機能の途中実装

# メッセージ編集画面で、整理されたメッセージを記述:
# feat: ユーザー検索機能を実装
#
# - 検索フォームUIの実装
# - 検索APIとの連携
# - 検索結果の表示機能

リモートにプッシュ

# rebase後はforce pushが必要(自分だけのブランチに限る)
git push --force-with-lease origin feature/search

# プルリクエストを作成
gh pr create --title "feat: ユーザー検索機能を実装" --body "..."

コンフリクト解消のテクニック

rebaseでもmergeでも、コンフリクトは発生します。効率的な解消方法を知っておきましょう。

コンフリクトの基本的な解消手順

# コンフリクトが発生したファイルを確認
git status

# コンフリクトマーカーの見方
<<<<<<< HEAD
// 現在のブランチの変更
const API_URL = '/api/v2/search';
=======
// 取り込もうとしている変更
const API_URL = '/api/v1/search';
>>>>>>> feature/search

# 手動で正しいコードに修正
const API_URL = '/api/v2/search';  // v2を採用

# ステージして続行
git add .
git rebase --continue  # rebaseの場合
# または
git commit             # mergeの場合

VS Codeのマージエディタを活用する

// VS Codeではコンフリクトファイルを開くと
// "Accept Current Change" / "Accept Incoming Change" /
// "Accept Both Changes" / "Compare Changes"
// のボタンが表示される

// 3-Way Merge Editorを使うとさらに分かりやすい
// Settings > "merge editor" で有効化

rebaseのコンフリクトで困ったときの対処法

# rebaseの途中で状況が分からなくなったら
git rebase --abort  # すべてを中断してrebase前の状態に戻る

# rerereを有効化しておくと、同じコンフリクトの解消を記録・再利用
git config --global rerere.enabled true
# → 同じコンフリクトパターンが出た場合、自動的に前回の解決方法が適用される

まとめ:チームで統一したルールを決める

rebaseとmergeの使い分けに「唯一の正解」はありません。重要なのは、チーム全体でルールを統一し、一貫性を保つことです。以下に推奨されるルールをまとめます。

  • ローカルの整理にはrebaseを使う:プッシュ前のfeatureブランチのコミット整理、mainへの追従
  • 共有ブランチの統合にはmergeを使う:プルリクエストのマージ、リリースブランチの統合
  • 共有ブランチではrebaseしない:main、develop等、複数人が参照するブランチのrebaseは禁止
  • force pushは--force-with-leaseを使う:意図しない上書きを防止
  • プルリクエスト前にコミットを整理する:インタラクティブrebaseでレビューしやすい履歴にする

rebaseを「危険なコマンド」として避ける必要はありません。ルールを決めて安全な範囲で使えば、きれいなGit履歴を保ちながら効率的にチーム開発を進められます。まずは自分のローカルブランチでインタラクティブrebaseを試してみることから始めてみてください。

#Git#rebase#merge
共有:
無料メルマガ

週1回、最新の技術記事をお届け

AI・クラウド・開発の最新記事を毎週月曜にメールでお届けします。登録は無料、いつでも解除できます。

プライバシーポリシーに基づき管理します

AI活用のヒントをお探しですか?お気軽にご相談ください。

まずは話だけ聞いてもらう