Git の merge と rebase: チームではどちらを選ぶか
merge と rebase はどちらもブランチの変更を統合しますが、履歴の扱いが違います。merge は既存のコミットを残してマージコミットを追加し、rebase は自分のコミットを新しい基点の上に書き直して履歴を一直線にします。実践的な目安は、rebase は自分だけのコミットを整えるために使い、merge は完成した作業を共有ブランチに取り込むために使うことです。
要点: merge と rebase の違い
「merge と rebase のどちらが優れているか」という議論は、どちらのコマンドが良いかではなく、プロジェクトの履歴をどのように見せたいかの問題です。どちらのコマンドも、最終的には同じコードになります。違うのは、ログに記録される経路です。
- merge は、2つの履歴の流れをつなぎ、作業が並行して行われたことを正直に記録します。既存のコミットは変わらず、親を2つ持つマージコミットが追加されます。
- rebase は、あなたのコミットを、最新の基点ブランチから作業を始めたかのように書き直します。履歴は一直線に見えますが、コミットには新しいハッシュが付きます。
それぞれのコマンドの仕組み(フラグ、コンフリクトの解決、対話モード)は別の記事で解説しています。ここでは、どちらの戦略を選ぶかだけを扱います。
両者の履歴の見え方
出発点は次のとおりです。feature を分岐させて2つのコミットを作った後、main には他の人のコミットが2つ追加されています。
D---E feature
/
A---B---C---F---G main
main に git merge feature を実行した後は、次のようになります。
D---E
/ \
A---B---C---F---G---M main
feature で git rebase main を実行し、main を進めた後は、次のようになります。
A---B---C---F---G---D'---E' main
1つ目の場合、履歴から、機能が並行して開発され、いつ統合されたかが分かります。2つ目の場合、履歴は順序立った一本の流れとして読めますが、並行して作業したという事実は失われます。D' と E' は rebase の時点で作られた新しいコミットだからです。
merge と rebase の比較表
| 比較項目 | git merge | git rebase |
|---|---|---|
| 既存のコミット | 変わらない | 新しいハッシュで作り直される |
| 履歴の形 | マージコミットを含む枝分かれしたグラフ | 一直線 |
| 共有ブランチでの安全性 | 常に安全 | 公開済みのコミットには危険 |
| force push が必要か | 不要 | サーバーにあるブランチなら必要 |
| コンフリクト | マージコミットで一度だけ解決 | 移すコミットごとに出現しうる |
| 「どこから来たか」の情報 | 残る | 失われる |
| 機能単位の取り消し | git revert -m 1 でマージコミットを1回 | コミットごと、または範囲ごとに revert |
git bisect や git log との相性 | グラフが読みにくい | 一本の流れで分かりやすい |
| 習得の難しさ | 低い | 高い。履歴の書き換えを理解する必要がある |
| コミットの整理(squash、reword) | 全体の --squash だけ | rebase -i で柔軟に行える |
merge の長所と短所
長所
- 履歴を壊さない。 公開済みのものはすべて残ります。あなたの操作の後で、同僚が直す必要はありません。
- コンフリクトは一度だけ解決する。 ブランチに30個のコミットがあっても、途中の各ステップではなく、最終的な状態と向き合えばよいのです。
- 文脈が残る。 マージコミットを見れば、どのタスクがいつ主流のブランチに入ったかが分かり、そのタスク全体を簡単に取り消せます。
短所
- グラフが複雑になる。 頻繁に同期すると、「Merge branch 'main' into feature」のようなコミットが増え、
git log --graphが鉄道の路線図のようになります。 - 作業中のコミットが履歴に残る。 「wip」「fix」「もう少し修正」などは、まとめなければ主流のブランチに永久に残ります。
rebase の長所と短所
長所
- 一直線で読みやすい履歴。 各コミットは決まった位置にあり、
git logは変更の一覧として読め、git bisectで不具合のコミットを見つけやすくなります。 - レビュー前にコミットを整えられる。 まとめる、名前を変える、並べ替えることができます。
- 同期のためのマージコミットが生まれない。
短所
- 履歴を書き換える。 公開済みの共有ブランチを rebase すると、それを取得したすべての人の作業が壊れます。
- サーバー上のブランチには force push が必要。 そのため、規律と
--force-with-leaseが求められます。 - コンフリクトが繰り返し起きる。 同じ箇所が連続するコミットで何度も衝突することがあります。
- 途中のコミットがビルドできないことがある。 新しい基点に移した後、古いコミットは新しい環境でテストされていません。
どんなときに使うか
rebase は自分のローカルの作業に
pull request の前に自分のブランチを更新する場合は git pull --rebase を使います。レビューに向けてブランチを整え、作業中のコミットを片付けたいなら git rebase -i を使います。個人の feature ブランチが main より遅れているなら git rebase origin/main を使います。いずれも、書き換えられるのは、あなただけが見ていたコミット、またはあなただけが持っているコミットです。
merge は共有ブランチと統合に
完成した機能を main や develop に取り込むとき、複数の人が同じブランチで作業しているとき、リリースブランチを主流のブランチに戻すとき、などに merge を使います。ここで大切なのは、すでに公開されたコミットに触れないことです。その意味で、merge が唯一の正しい方法になります。
シンプルな判断基準
コミットがあなただけのものなら rebase してもよい。誰かがすでに取得しているなら merge だけにする。
チームで使える運用パターン
| パターン | 動作 | 向いているチーム |
|---|---|---|
| merge のみ | 機能は --no-ff で統合し、同期は git merge main | Git に不慣れなチーム、完全な時系列とシンプルな取り消しが必要なチーム |
| rebase + merge | 統合の前に main へ rebase し、その後 --no-ff でマージコミットを作る | きれいな履歴と機能単位の区切りの両方が欲しいチーム |
| rebase + fast-forward | rebase してから早送りで統合し、履歴を完全に一直線にする | 整ったアトミックなコミットを作る小規模なチーム |
| squash merge | pull request ごとに main へ1つのコミットとして入れる | 短い作業が多く、ブランチ内の詳細な履歴が不要なチーム |
GitHub と GitLab では、これらのパターンは「Create a merge commit」「Rebase and merge」「Squash and merge」のボタンに対応します。リポジトリの設定で、選んだ方式だけを許可するとよいでしょう。そうしないと、履歴のスタイルが混ざってしまいます。
選択を固定する設定
# merge の余計なコミットを作らずに pull する
git config --global pull.rebase true
# または: 早送りの場合のみ pull し、それ以外はエラーで止める(自分で判断する)
git config --global pull.ff only
# 機能の統合では常にマージコミットを作る
git config --global merge.ff false
merge.ff false には注意が必要です。merge モードの git pull にも影響します。グローバルに設定するのではなく、機能を統合するときに --no-ff を明示的に付ける人も多いです。
例: 同じタスクを2つの方式で
実際の場面で比べてみましょう。feature/payment ブランチで3日間、支払い機能を作っています。その間に main には他の人のタスクが5つ入りました。pull request の前に、最新の main を取り込み、動作を確認する必要があります。
merge の場合
git switch feature/payment
git fetch origin
git merge origin/main
# コンフリクトを一度だけ解決し、git add、git commit
git push
ブランチには「Merge remote-tracking branch 'origin/main' into feature/payment」というコミットが追加されます。push は普通に通り、ブランチを見ていた同僚も特に困りません。毎日このように同期すると、ブランチには同じようなコミットが3つ並び、squash せずに統合すれば、それらも main に入ります。
rebase の場合
git switch feature/payment
git fetch origin
git rebase origin/main
# 各コミットでコンフリクトを解決: git add、git rebase --continue
git rebase -i origin/main # 任意: 作業中のコミットをまとめる
git push --force-with-lease
ブランチは、今朝、最新の main から作られたように見えます。余計なコミットはなく、レビューする人は変更を段階ごとに読めます。代償は、強制 push が必要なことと、そのブランチに他の人がコミットしないよう注意しなければならないことです。
最終的に main に残るもの
pull request で統合すると、この違いは残ります。merge の方式では、枝分かれと同期のための統合コミットを含むグラフになります。rebase の方式では、機能のコミットが整然とした一本の鎖になり、早送りまたは1つのマージコミットで統合できます。解決したコンフリクトが同じであれば、コードの内容は同じになります。
チームでの決め方
いくつかの質問に答えてみてください。そのブランチで複数人が作業していますか。もしそうなら、そのブランチで rebase は使えません。チームの Git の経験はどうですか。新人が多いなら、まず merge と squash merge から始めましょう。これらは失敗を許容してくれます。監査や障害の振り返りに完全な時系列が必要ですか。それなら機能のマージコミットが役立ちます。読みやすい git log と頻繁な git bisect が重要ですか。それなら rebase と一直線の履歴が向いています。
決めた方式を CONTRIBUTING や README に書き、主要なブランチを保護しましょう。force push の禁止、pull request の必須化、許可する統合方式の指定などです。設定で強制されているルールは、口頭の取り決めより確実に守られます。
よくある誤解
- 「rebase は上級者向け、merge は初心者向け」。 そうではありません。両者は目的の違う道具です。経験豊富な開発者は両方を使います。
- 「マージコミットはゴミだ」。 同期のためのマージコミットはノイズになることが多いです。しかし、機能を統合するマージコミットは有用です。そこを見れば、タスク全体が分かり、取り消しもできます。
- 「rebase すればコンフリクトが減る」。 コンフリクトの数は変わりません。同じ行が変わるからです。rebase はコンフリクトをコミットごとに分けるだけで、merge は一度にまとめて解決します。
- 「rebase するとコミットが消える」。 古いコミットは
git reflogに残り、ガベージコレクションで消えるまでは復元できます。
まとめ
merge と rebase の選択は、正直な時系列を取るか、読みやすい一直線の履歴を取るかの選択です。merge は安全で文脈を残し、rebase は整然としていますが、コミットを書き換えます。最も確実な方法は組み合わせることです。自分だけのブランチは、自分のものである間に rebase して整えます。完成した作業は、チームのルールに従って merge または squash で共有ブランチに入れます。何より大切なのは、方式を決めて設定で固定することです。両方の戦略、そしてチームでのブランチの扱い方は、Gitコースで実践的に扱っています。
よくある質問
git pull には merge と rebase のどちらを使うべきですか?
自分のブランチを更新するなら rebase が便利です。自分の未公開のコミットが、マージコミットを挟まず、新しいリモートのコミットの上に乗ります。git config --global pull.rebase true で有効になります。
1つのプロジェクトで merge と rebase を混ぜてもよいですか?
はい。これが最も一般的な方法です。自分のブランチには rebase、共有ブランチには merge を使います。ただし、そのルールをチーム全員が知っていることが条件です。
新人にはどちらを勧めますか?
まず merge から始めましょう。履歴を書き換えないため、壊しにくいです。rebase は、reflog と force push を理解してから、自分のローカルのブランチで練習してください。
戦略の選択で最終的なコードは変わりますか?
変わりません。コンフリクトを同じように解決すれば、ファイルの最終状態は同じです。違いが出るのは、記録されるコミット履歴だけです。
squash merge と rebase の違いは何ですか?
squash merge はブランチ全体を、主流のブランチへの新しい1つのコミットにまとめます。rebase は各コミットを個別に移します。どちらも一直線の履歴になりますが、squash は手順ごとの詳細を失います。