shibomb

Gitリベース vs マージ戦略:チーム開発で履歴を整理するプロの作法

チーム開発でGitを使っていると、いつの間にかコミット履歴が複雑に絡み合い、どこでバグが混入したのか追うのが大変…なんて経験はありませんか?「とりあえず git merge してるけど、git rebase の方が良いって聞くし…」と、マージとリベースの使い分けに悩んでいる方も多いはずです。この記事では、Gitの2大合流戦略であるマージとリベースの仕組みから、チーム開発で履歴をクリーンに保つための具体的な使い分けまで、プロの作法を徹底解説します。あなたの バージョン管理 スキルを次のレベルへ引き上げましょう!

Gitの基本を再確認!ブランチとコミットの役割

Git のブランチ戦略を語る前に、基本となる「コミット」と「ブランチ」の役割を簡単におさらいしましょう。Gitでは、変更の記録を「コミット」という単位で保存します。そして、コミットは時系列に沿って一列に繋がっていきます。これがプロジェクトの歴史そのものです。

「ブランチ」は、この歴史の流れから分岐して、他の開発者や他の機能に影響を与えることなく、独立して作業を進めるための仕組みです。例えば、main ブランチというメインの歴史から feature/new-login のようなブランチを切ることで、新しいログイン機能の開発を安全に進められます。

# mainブランチから新しいfeatureブランチが分岐した状態
A---B---C   <- main
     \
      D---E <- feature/new-login

この図では、コミット B の時点から feature/new-login ブランチが分岐し、DE という2つのコミットが追加されています。一方、その間にも main ブランチは C へと進んでいます。問題は、この feature/new-login ブランチでの作業が完了した時、どうやって main ブランチに合流させるか、です。ここで登場するのが「マージ」と「リベース」です。

マージ戦略のメリット・デメリット:シンプルな合流の裏側

git merge は、最も基本的で直感的なブランチの合流方法です。分岐した2つのブランチの最終的な状態を統合し、その結果を新しい「マージコミット」として記録します。

先ほどの例で、main ブランチ上で git merge feature/new-login を実行すると、コミット履歴は以下のようになります。

# マージ後のコミット履歴
A---B---C---F   <- main
     \     /
      D---E   <- feature/new-login

新しく作られたコミット F がマージコミットです。このコミットは「CE の変更点を統合しました」という事実を記録しています。

マージ戦略のメリット

  • 履歴の正確性: 分岐して合流したという事実がそのまま記録されます。いつ、どのブランチがマージされたのかが一目瞭然です。
  • 安全性: 既存のコミット履歴を書き換えることがないため、チームメンバーが共有しているブランチに対して行っても問題が起きにくいです。

マージ戦略のデメリット

  • 履歴の複雑化: 開発が進むにつれて多数のブランチがマージされると、コミットログが非常に複雑になります。どの変更がどの機能に対応するのかを追うのが困難になりがちです。
  • 不要なマージコミット: ちょっとした変更を取り込むだけでもマージコミットが生成され、ログが冗長になることがあります。

リベース戦略のメリット・デメリット:クリーンな履歴の代償

git rebase は、ブランチの歴史を「書き換える」ことで合流を実現します。具体的には、合流させたいブランチ(例: feature/new-login)のコミットを一旦取り消し、合流先のブランチ(例: main)の最新コミットの直後に、一つずつコミットを再生(適用)し直します。

feature/new-login ブランチ上で git rebase main を実行すると、コミット履歴は以下のように再構築されます。

# リベース後のコミット履歴
A---B---C           <- main
         \
          D'---E'   <- feature/new-login

DE は消え、代わりに main の先端である C の上に D'E' という新しいコミットが作られました。内容は同じですが、コミットIDは変わります。この後 main ブランチで feature/new-login をマージすると、歴史が一直線に繋がっているため、単純に main のポインタが E' に移動するだけです (これをFast-Forwardマージと呼びます)。

リベース戦略のメリット

  • クリーンな履歴: コミット履歴が一直線になり、プロジェクトの進捗が非常に分かりやすくなります。
  • コミットの整理: インタラクティブリベース (rebase -i) を使えば、複数のコミットを1つにまとめたり (squash)、コミットメッセージを修正したりと、マージ前に履歴を綺麗に整形できます。

リベース戦略のデメリット

  • 歴史の改変: コミット履歴を書き換えるため、いつどこで分岐したかの情報が失われます。
  • コンフリクト解決の複雑さ: リベースはコミットを1つずつ適用するため、コンフリクトが発生すると、コミットの数だけ解決作業を繰り返す必要があります。
  • 共有ブランチでの危険性: チームで共有しているブランチ (例: main) に対してリベースを実行してはいけません。他のメンバーのローカルリポジトリと歴史が食い違い、深刻な問題を引き起こす原因になります。これはGitを使う上での鉄則です。

実践!開発現場で役立つリベース vs マージの使い分けケーススタディ

では、具体的にどのような場面で リベースマージ を使い分けるのが効果的なのでしょうか。多くの開発現場で採用されているプラクティスを3つのケースで見ていきましょう。

ケース1: 開発中のブランチを最新の状態に保つ時 → リベース

feature ブランチで作業している間に、main ブランチが他のメンバーによって更新されるのは日常茶飯事です。この最新の変更を自分のブランチに取り込みたい場合、git rebase が最適です。

# featureブランチにいる状態で実行
git fetch origin
git rebase origin/main

これにより、自分の作業(コミット)が main ブランチの最新版の上で行われたかのように歴史が再構築されます。不要なマージコミットがローカルブランチに増えるのを防ぎ、後のプルリクエストをクリーンに保てます。

ケース2: プルリクエスト前にコミットを整理する時 → インタラクティブリベース

プルリクエスト(マージリクエスト)を作成する前は、自分の作業履歴を見直す絶好の機会です。「typo修正」「WIP (作業中)」といった細かすぎるコミットが散らかっていると、レビュー担当者が変更の意図を把握しにくくなります。

インタラクティブリベース (git rebase -i) を使えば、複数のコミットを意味のある単位で1つにまとめたり (squash)、コミットメッセージを分かりやすく書き換えたり (reword) できます。

# mainブランチから分岐して3つのコミットをした場合
git rebase -i main

# エディタが開き、コミットを編集できる
pick a1b2c3d fix: typo
squash d4e5f6a feat: add user input validation
squash g7h8i9j feat: add error message

こうすることで、レビュー担当者は1つの綺麗なコミットを確認するだけで済み、レビューの負担が大幅に軽減されます。

ケース3: 開発が完了したブランチを統合する時 → マージ

プルリクエストが承認され、いよいよ feature ブランチを main ブランチに統合する時です。この場面では、git merge を使うのが一般的です。

特に、--no-ff (no-fast-forward) オプションを付けてマージすることが推奨されるケースが多いです。

# mainブランチにいる状態で実行
git merge --no-ff feature/new-login

たとえFast-Forward可能な状態であっても、このオプションは意図的にマージコミットを作成します。これにより、「ここでログイン機能が追加された」という事実がコミット履歴に明確に記録されます。後から機能単位で変更を追跡したり、問題があった場合にマージコミット単位で取り消したり (git revert) するのが非常に簡単になります。

チーム開発で合意する!効果的なブランチ戦略の決め方

リベースとマージのどちらが絶対的に優れているということはありません。重要なのは、チーム開発 において全員が同じルールで運用することです。チーム内で ブランチ戦略 を決め、それをドキュメント化し、全員で遵守することが、クリーンな履歴を保つ鍵となります。

以下は、多くのチームで採用されている基本的なルールの例です。

  1. main ブランチは常に安定させる: main ブランチには直接コミットせず、プルリクエスト経由でのみマージする。
  2. 共有ブランチはリベースしない: maindevelop のように、複数人が利用するブランチの歴史を書き換える git rebase は禁止する。
  3. 作業ブランチはリベースで綺麗に: プルリクエストを出す前に、開発者は自分の feature ブランチを main の最新版に対してリベースし、コミットを整理する。
  4. 統合はマージコミットで: プルリクエストのマージは --no-ff オプションを使い、機能追加の履歴を明確に残す。

GitHubやGitLabなどのプラットフォームには、これらのルールを強制するための「ブランチ保護ルール」機能があります。例えば、main ブランチへの直接プッシュを禁止したり、プルリクエストが承認されるまでマージできないように設定したりできます。こうしたツールを活用して、ルールを仕組み化することも非常に有効です。

万が一の時も安心!Gitのトラブルシューティングとコンフリクト解決術

リベースやマージを使っていると、必ずと言っていいほど「コンフリクト(衝突)」に遭遇します。コンフリクトとは、同じファイルの同じ箇所を、異なるブランチで別々に変更してしまった場合に発生するものです。慌てずに対応しましょう。

コンフリクトが発生すると、Gitは該当ファイルに以下のようなマーカーを挿入します。

<<<<<<< HEAD
// こちらは現在のブランチ(HEAD)での変更です
const message = "Hello, world!";
=======
// こちらは取り込もうとしているブランチでの変更です
const message = "Hello, Git!";
>>>>>>> feature/new-message

やるべきことは、このマーカーを削除し、どちらの変更を採用するか、あるいは両方を組み合わせるかを判断してファイルを編集することです。

修正が完了したら、以下のコマンドでGitに解決したことを伝えます。

  1. 修正したファイルをステージングする: git add <conflicted-file>
  2. 操作を続行する:
    • マージの場合: git commit
    • リベースの場合: git rebase --continue

もしリベース中にパニックになったら、git rebase --abort を実行すれば、リベースを始める前の状態に安全に戻せます。また、「間違えてコミットを消してしまった!」という時も、git reflog コマンドを使えば過去の操作履歴を確認し、失われたコミットを復活させることが可能です。reflog はあなたのGit操作を常に記録してくれている、最後の頼みの綱です。ぜひ覚えておいてください。

関連記事