Gitの基礎 - rebaseとmergeの使い分け

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動

概要

Gitにおいて、mergeコマンドとrebaseコマンドは、異なるブランチの変更を統合するためのコマンドである。
しかし、統合の方針に違いがある。

ここでは、masterブランチの過去のコミットから派生した機能開発用のtopicブランチの変更を、masterブランチに取り込むという状況で、2つの方法を比較する。


merge / rebaseの主な違い

履歴の形状

  • merge
    ブランチの履歴がそのまま保持されて、マージコミットによって結合点が作られる。(木構造)
  • rebase
    コミットを付け替えることにより、直線的な履歴になる。


コミットの同一性

  • merge
    元のコミットのSHA-1ハッシュが保持される。
  • rebase
    新しいコミットが作られるため、SHA-1ハッシュが変更される。


コンフリクト解決

  • merge
    マージ時に1回だけコンフリクトを解決する。
  • rebase
    付け替える各コミットにおいて、潜在的にコンフリクトが発生する可能性がある。



mergeを使用すべき場合

  • フィーチャーブランチの履歴を残す場合
  • 既にリモートで共有されているブランチの場合
  • コンフリクト解決を1度で済ませる場合
  • チーム開発で作業の証跡を残す場合



rebaseを使用すべき場合

  • 直線的な履歴が必要な場合
  • ローカルでのみ使用しているブランチの場合
  • 細かい作業履歴を整理する場合
  • master / mainブランチの最新変更を取り込む場合



Publicブランチでのrebase禁止

既にプッシュされたブランチをrebaseする場合、他の開発者の環境で問題が発生する。

# 絶対に避けるべき操作
git checkout main
git rebase feature



rebase後のプッシュ

--force-with-leaseオプションとは、他の開発者が同じブランチを更新していないことを確認できる安全なオプションである。

# rebase後に強制プッシュが必要
git push --force-with-lease origin feature



マージ戦略の一貫性

チーム内で統一されたマージ戦略を実施することが重要となる。

# マージコミットを必ず作成する場合
git merge --no-ff feature

# rebaseした後にマージする場合
git checkout feature
git rebase main
git checkout main
git merge --ff-only feature



推奨される方法

作業開始時の基点更新

git checkout main
git pull
git checkout -b feature

# 作業開始


定期的な同期 (rebaseの場合)

git checkout feature
git rebase main

# コンフリクト解決
git push --force-with-lease origin feature


マージ前の履歴整理

# コミットを整理
git rebase -i HEAD~3

# スカッシュマージ
git merge --squash feature



mergeを使用して統合する

下図のように、topicブランチで開発する間において、masterブランチのコミットが先に進んでいるとする。
この時、materブランチにtopicブランチをmergeすると、コンフリクト (競合、衝突) が発生する。

Git Merge Rebase 1.png


しかし、masterブランチ上でコンフリクトを解決するのは好ましくない。
これは、解決の結果が正常に動作するかどうか実証する必要があるからである。

そこで、まず、コンフリクトの解決の実証をローカルのtopicブランチで行うため、masterブランチ側の変更をtopicブランチにmergeする。
topicブランチに取り込むことにより、コンフリクトを解決する。

次に、masterブランチをチェックアウトして、topicブランチの変更をmasterブランチにmergeする。
この時、--no-ffオプションを付加しない限りは、fast forward mergeとなる。

これにより、topicブランチのコミットと完全に同一のコミット (sha-1が同じ) が、masterブランチのコミットとして記録される。

チーム開発の場合、masterブランチへの最終マージにおいては、masterブランチの管理者に対してプルリクエストを送信して実施してもらう場合もある。


rebaseを使用して変更の起点を移動する

rebaseは、再度(re)起点を定める(base)という意味合いがある。

下図に、最初の状態 (rebase前) を示す。
mainブランチとtopicブランチが分岐しており、両ブランチで並行開発が行われているものとする。

Git Merge Rebase 2.png
図. rebase前の状態



まず、統合前の状態では、masterブランチとtopicブランチの間にコンフリクトが発生する。
それを避けるため、topicブランチの起点をmasterブランチの最新のコミットで置き換える。

コンフリクトがあれば個別に解決する必要がある。
rebaseでは、topicブランチのコミットを1つずつ順番にmasterブランチの最新の先端に付け加える処理が発生する。

例えば、topicブランチ上において、masterブランチとコンフリクトが発生するファイルを何度も繰り返し変更している場合は、その都度、競合が発生する。
そのため、コミットの多いtopicブランチをrebaseする場合は作業が煩雑になる。
事前に、topicブランチの履歴をrebase -iコマンド等を実行して簡略化すると緩和できる。

  1. topicブランチの起点が、mainブランチの最新コミットに移動する。
  2. topicブランチのコミットが順次適用されて、新しいコミット (3'、6'、8') として再作成される。
    この過程でコンフリクトが発生する可能性があり、その都度解決が必要となる。
Git Merge Rebase 3.png
図. rebase後の状態 (topicブランチの起点が移動)



ここでは、rebaseした後のコンフリクトの解決と実証が正常に終了したものとする。

次に、この変更をmasterブランチに取り込む。
masterブランチにチェックアウトして、git merge --no-ff <ブランチ名 例: topic>コマンドを使用する。
必ず、--no-ffオプションを付加すること。

この時、topicブランチのコミットログがmasterブランチ側に残らないように配慮する必要がある。
--no-ffオプションにより、fast forwardを行わない独立したコミットが作成される。

masterブランチのコミットログを見ると、基本的には直列の履歴かつ変更コミットが機能ごと (topicブランチごと) に独立して見える。

  1. --no-ffオプションにより、マージコミットが明示的に作成される。
  2. topicブランチの変更が1つのまとまりとしてmainブランチに統合される。
  3. mainブランチの履歴が直線的に保たれる。
Git Merge Rebase 4.png
図. 最終的なmerge (--no-ff) 後の状態




rebase時のコンフリクトが発生する条件

下図において、コンフリクトが発生する条件は、
topicブランチのコミット (3', 6', 8') が、mainブランチの新しいコミット (4, 5, 7) と「同じファイルの同じ部分」を変更している場合のみである。

Git Merge Rebase 5.png


  1. コミット3'のrebase時
    • masterブランチの4では、別ファイル(file_b.txt)の変更なのでコンフリクト無し。
    • masterブランチの5において、同じファイル (a.txt) を変更しているためコンフリクトが発生。

  2. コミット6'のrebase時
    • これもa.txtファイルの変更であるため、masterブランチの変更とコンフリクト発生の可能性がある。

  3. コミット8'のrebase時
    • masterブランチの7は、別ファイル (c.txt) の変更であるためコンフリクト無し。
    • ただし、masterブランチの4で変更されたb.txtファイルとコンフリクト発生の可能性がある。


したがって、

  • 異なるファイルの変更
    コンフリクトは発生しない。
  • 同じファイルでも異なる箇所の変更
    コンフリクトは発生しない。
  • 同じファイルの同じ箇所を変更
    コンフリクトが発生する。


このため、rebaseの作業量は以下に示す要因によって大きく変わる。

  • 変更ファイルの重複度
  • 変更箇所の重複度
  • topicブランチのコミット数



備考

masterブランチを進める観点で見ても、作成される履歴に違いが出る。

一般的には、どのような履歴を残すかによって使い分けるが、masterブランチなどの統合ブランチの履歴をシンプルに保つ等の目的で、
以下のような方針で運営するケースもよく見られる。

masterブランチ等の統合用ブランチに、別ブランチをmerge(または、pull request)する前に、必ず最新のmasterブランチでrebaseしてからmerge(または、pull request)する。
これらは、チームの運営方針に合わせる。