【Git】rebaseとmergeの違い:履歴をどう残すか

Git

こんにちは、かつコーチです。

ここまでのGit編で、branch・merge・reset・revert・stashと、実務でよく使う操作を一通り解説してきました。

今回は少し上級のテーマとして、git rebasegit mergeの違いを掘り下げます。

前提知識として、branchとmergeの基本操作は理解している前提で進めますので、必要であれば該当記事も合わせて確認してください。

rebaseとmergeの違いとは

合流させる(merge)vs 土台ごと引っ越す(rebase)

mergeは、2つの下書きをその場で合流させて1つの原稿にまとめるイメージでした。

一方rebaseは、下書きの土台ごと、書いた場所を引っ越すイメージです。

feature-loginブランチが古いmainの上に生えた枝だとすると、rebaseは「その枝をいったん切り離し、最新のmainの上に、同じ内容のコミットを1つずつ積み直す」操作にあたります。

見た目の結果としては「最初から最新のmainの上で作業していたかのような、まっすぐな履歴」ができあがります。

履歴の見た目がどう変わるか

図で比較すると、その違いは明確です。

分岐前:
main:     A---B
               \
feature:        C---D

mergeした場合(マージコミットEが追加される):
main:     A---B-------E
               \      /
feature:        C----D

rebaseした場合(C・DがB以降に積み直され、C'・D'になる):
main:     A---B---C'---D'

mergeは分岐と合流の跡がそのまま履歴に残るのに対し、rebaseは分岐がなかったかのような一直線の履歴になります。

「何が起きたかの記録を残すか」「読みやすい直線的な履歴を優先するか」という思想の違いが、そのまま見た目の違いに表れています。

git rebaseの基本

使い方

feature-loginブランチの内容を、最新のmainの上に積み直す場合は次のようにします。

# feature-loginブランチにいる状態で実行する
git switch feature-login
git rebase main

これで、feature-login上のコミットが、最新のmainの続きとして1つずつ再適用されます。

コンフリクト時の対応(rebase –continue / –abort)

rebase中にコンフリクトが起きた場合、mergeのときとは対応の流れが少し異なります。

git rebase main
CONFLICT (content): Merge conflict in report.txt

この場合は、通常のコンフリクト解決と同様にファイルを修正してgit addした後、git commitではなくrebase --continueで先に進めます。

# コンフリクトを解決した後
git add report.txt
git rebase --continue

再適用するコミットが複数ある場合、コミットの数だけこのやり取りを繰り返す可能性がある点はmergeと大きく異なります。

途中で心が折れそうになったら、いつでも中断できます。

git rebase --abort

mergeとrebaseの比較

比較表

観点mergerebase
履歴の見た目分岐・合流がそのまま残る一直線に整理される
コンフリクト対応1回にまとめて対応コミットごとに複数回発生し得る
既存コミットへの影響なし(新しいコミットを追加するだけ)あり(コミットのハッシュ値が変わる)
チーム運用での安全性高い(誰と作業していても安全)条件付き(共有済み履歴には要注意)

それぞれのメリット・デメリット

mergeのメリットは、なんといっても安全性です。

既存のコミットを一切書き換えないため、誰がどのブランチで作業していても、事故が起きにくい操作です。

デメリットは、頻繁にマージを繰り返すプロジェクトでは、履歴が枝分かれだらけになり、後からgit log --graphを見ても流れを追いにくくなることです。

rebaseのメリットは、履歴が一直線に整理され、あとから見返したときに変更の流れを追いやすいことです。

デメリットは、コミットのハッシュ値が変わってしまう点にあります。

これが、次に説明する「golden rule」につながります。

実務での使い分け・ベストプラクティス

個人ブランチではrebase、共有ブランチではmerge

多くの現場で採用されている方針はシンプルです。

自分だけのローカルブランチではrebaseで履歴を整理し、他人と共有するブランチにはmergeで取り込む。

具体的には、次のような使い分けになります。

  • 自分のトピックブランチをmainの最新に追従させたい → rebase
  • 完成したトピックブランチをmainに取り込みたい → merge(プルリクエスト経由が一般的)

golden rule:公開済み履歴をrebaseしない

rebaseを扱ううえで、最も重要な原則が1つあります。

「すでにpushして他人と共有しているコミットは、rebaseしてはいけない」というものです。

理由は、rebaseによってコミットのハッシュ値が変わってしまうためです。

同じ内容のコミットでも、rebase後は別物として扱われるため、すでにそのコミットをpullしていたメンバーの履歴と食い違いが生じます。

私自身、検証環境で試していたブランチをうっかり共有ブランチだと勘違いしてrebase・強制pushしてしまい、チームメンバーの手元で謎のコンフリクトが多発するという事故を経験したことがあります。

原因を突き止めるまで、お互いに「何が起きているのか分からない」状態で30分以上時間を溶かしました。

この経験から、「共有ブランチかどうか怪しいときは、rebaseではなくmergeを選ぶ」を徹底するようになりました。

interactive rebaseで履歴を整える

余談として、rebaseにはコンフリクト対応以外にも便利な使い方があります。

-i(インタラクティブ)オプションを使うと、pushする前の自分のコミットを整理できます。

# 直近3件のコミットを対象にインタラクティブrebaseを開始
git rebase -i HEAD~3

エディタが開き、picksquashrewordといったコマンドを使って、コミットの統合やメッセージの修正が行えます。

「作業中はこまめにコミットし、pushする前にinteractive rebaseでコミットを整理して読みやすい履歴にする」というのは、rebaseの安全な活用方法の代表例です。

あくまで自分のローカルでpush前という条件付きである点を忘れないようにしてください。

まとめ

この記事のポイント

  • mergeは分岐と合流をそのまま履歴に残す「2つの下書きの合流」、rebaseは土台ごと積み直す「下書きの引っ越し」
  • mergeは安全性が高く、rebaseは履歴が一直線で読みやすくなる代わりにコミットのハッシュ値が変わる
  • 個人のローカルブランチではrebase、共有ブランチへの取り込みはmergeという使い分けが基本
  • 公開済み・共有済みの履歴はrebaseしない、というgolden ruleを必ず守る
  • interactive rebaseは、push前の自分のコミットを整理する強力な手段になる

次に読むべき記事

rebaseとmergeの違いを理解したら、最後にGit編の総まとめとして、よくあるトラブルとその解決法を確認しておきましょう。

→ 次の記事:よくあるGitのトラブルと解決法まとめ


タグ: Git, 上級者向け, 比較検証

タイトルとURLをコピーしました