Skip to main content

git rebase と git merge の使い方と使用シーンを紹介します!

· 14 min read

git merge の紹介

マージの基本的な構文は:git merge <branch> です。

マージ操作は、複数のブランチの開発履歴を一つに統合し、全てのブランチのコミットを保持します。そして、マージ操作の完了を示す新しいマージコミット(merge commit)が追加されます。

ここで、以下のような fixmaster ブランチ、およびコミット履歴があるとします:

        A --- B --- C  fix
/
D --- E --- F --- G master

この時点で、fix ブランチの最新のコードを master ブランチにマージしたいと考えています。

master ブランチで git merge fix コマンドを実行することができます。

これにより、fix ブランチのコミット内容(現在のブランチから分岐して以来の全てのコミット)が master ブランチにマージされます。

その結果、コミット履歴は次のようになります:

        A --- B --- C  fix
/ \
D --- E --- F --- G --- H master

master ブランチのコミット履歴には、新しいマージコミット H が追加されます。

H のコミットには、fix ブランチの全ての A、B、C のコミットが含まれます。

git merge を避けるべき場面

git merge を使うと、ブランチのすべてのコミット履歴が保存され、さらに新しいマージコミットが追加されます。これは公共のブランチでは良いマージ戦略です

しかし、プライベートブランチや共有されていないブランチでこれを行うと、問題が生じやすいです

次のような featuremaster ブランチ、およびコミット履歴があるとします:

D --- E --- F --- G  master
\
A --- B feature

feature ブランチを開発している間に、master ブランチが二度更新されました。

feature ブランチのコードを develop ブランチにマージしてテストする前に、コードの整合性を保つために master ブランチの最新のコードを feature ブランチにマージする必要があります。

この時点で git merge を続けると、コミット履歴は次のようになります:

D --- E --- F --- G  master
\ \
A --- B --- H feature

つまり、feature ブランチのコミット履歴は次のようになります:

A --- B --- H(F と G が含まれている)  feature

H のコミットには master ブランチの最新のコミット、すなわち F と G が含まれます。

コードのマージが成功した後、feature ブランチのコードを git merge を使って公共のブランチ develop にマージします。

コミット履歴は次のようになります:

D --- E --- F --- G  master
\ \
A --- B --- H feature
\
X --- Y --- Z ----------- M develop

develop ブランチには M コミットが追加され、M コミットには feature ブランチのすべてのコミット(A、B、H)が含まれ、H コミットには master ブランチの F と G が含まれます。

この例では、コミット数がわずか数回でも、2 回のマージ後に feature ブランチと develop ブランチの Git コミット履歴がこれほど複雑になります。

コミット数が増え、マージ回数が増えると、コードレビューやデバッグやコードの履歴追跡が非常に困難になります。

私が経験した中で、コミット数が 200 を超える場合の MR がありましたが、これはメインブランチの最新コードをマージした結果です。

したがって、余分なコミット履歴を削除し、コミット履歴をクリーンに保つことが重要です。

これにより、コードのレビューが楽になり、コードのデバッグやコードの履歴追跡も理解しやすくなります。

git rebase の紹介

リベースの基本的な構文は: git rebase <branch> です。

コードの統合を行う際、不要なコミット履歴を取り除き、履歴をシンプルで直線的に保つには、git rebase を使用する必要があります。

git merge とは異なり、git rebase はマージコミットを作成せず、現在のブランチのコミットを基準ブランチの最新コミットに再適用します。これにより、現在のブランチの履歴が基準ブランチの最新コミットから直接派生したかのように見え、履歴が直線的でシンプルになります。

上記の git merge が適さない場面の問題解決

上記と同じ、以下のような featuremaster ブランチ、およびコミット履歴があるとします:

D --- E --- F --- G  master
\
A --- B feature

feature ブランチを開発している間に、master ブランチが二度更新されました。

feature ブランチのコードを develop ブランチにマージしてテストする前に、コードの一貫性を保つために master ブランチの最新コードを feature ブランチにマージする必要があります。

この時点で git merge ではなく git rebase を使用すると、コミット履歴は次のようになります:

D --- E --- F --- G  master
\
A --- B feature

つまり、feature ブランチのコミット履歴は次のようになります:

A --- B  feature

不要な H、F、G のコミットが含まれていません。

リベースが成功した後、feature ブランチのコードを git merge を使用して公共のブランチ develop にマージします。

コミット履歴は次のようになります:

D --- E --- F --- G  master
\
A --- B feature
\
X --- Y --- Z --------------- M develop

develop ブランチには M コミットが追加され、M コミットには feature ブランチの全てのコミット(A と B)が含まれます。

以前の git merge を使用した場合と比較して、develop ブランチに追加された M コミットには feature ブランチの全てのコミット(A、B、H)が含まれ、H には master ブランチの F と G のコミットが含まれています。

現在、M コミットには feature ブランチのコミット(A と B)のみが含まれています。

上記の例から、プライベートブランチや共有されていないブランチで git rebase を使用して基準ブランチの最新コードを取り込むことで、次のような利点があります:

  1. コミット履歴を直線的で分かりやすく保つ

    これにより、プロジェクトのコミット履歴が読みやすく理解しやすくなり、コードレビューが容易になります。

    また、デバッグやコードの遡りにおいても、コードの進化過程が理解しやすくなります。

  2. 不要なマージコミットを避ける

  3. 早期にコンフリクトを解決し、協力効率を向上させる

    git rebase によって早期にコンフリクトを解決することで、最終的なマージ時の複雑さを軽減できます。

    さらに、基準ブランチの最新の変更を定期的にプライベートブランチに適用することで、常に最新のコードベースに基づいて作業を行い、チームメンバー間のコードコンフリクトを減らすことができます。

コンフリクトがない場合

git rebase <branch> を実行した後、コードにコンフリクトがなければ、そのままリベースが完了しています。

その後は git push -f を使ってリモートブランチにコードをプッシュするだけです。

なぜ git push -f を使う必要があるのでしょうか?

git rebase を実行すると、Git は各コミットに新しいコミットオブジェクトを作成します。これらの新しいコミットオブジェクトは、内容が同じであっても元のコミットとは異なります。

そのため、ローカルブランチのコミット履歴がリモートブランチの履歴と一致しなくなります。

これらの変更をリモートリポジトリにプッシュしようとすると、Git はローカルのコミット履歴がリモートの履歴とコンフリクトしていると判断します。なぜなら、それらは異なるコミットハッシュを持っているからです。

これらの変更を強制的にプッシュするには、git push -f を使用する必要があります。これにより、リモートブランチの履歴が上書きされ、ローカルブランチと一致するようになります。

git push -f を使用する必要があるため、プライベートブランチや共有されていないブランチのみでリベース操作を行うことが重要です。

コードのコンフリクトの処理

git rebase <branch> を実行した際にコードにコンフリクトが発生した場合、Git は次のようなメッセージを表示します:

(base) nansenho@mb-nansyou-01 dragonfly_frontend % git rebase develop
Auto-merging tests/e2e/specs/error/404.spec.ts
CONFLICT (add/add): Merge conflict in tests/e2e/specs/error/404.spec.ts
error: could not apply 03652542... fix:404エラーページのe2eテスト
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 03652542... fix:404エラーページのe2eテスト

このメッセージは、コンフリクトを解決するための三つの方法を教えてくれます:

  • git rebase --continue

    git rebase の途中でコンフリクトが発生し、手動で全てのコンフリクトを解決した後、git add <file> を使って解決済みのファイルをマークします。

    その後、git rebase --continue を実行してリベース操作を続行します。

    リベース操作が完了したら、git push -f を使用してローカルの変更をリモートブランチにプッシュします。

  • git rebase --abort

    リベース中に問題が発生し、現在のリベース操作を中止することを決定した場合、git rebase --abort を使用できます。

    このコマンドは現在のリベース操作をキャンセルし、リベースを開始する前の状態にブランチを戻します。

  • git rebase --skip

    現在のコードコンフリクトを無視することを決定した場合、git rebase --skip を使用できます。

    このコマンドは現在のコンフリクトを引き起こしているコミットをスキップします。

実際にコンフリクトが発生した場合、最も推奨される方法は、手動でコンフリクトを解決した後に git rebase --continue を使用してリベースを続行することです。

解決できないコンフリクトが発生した場合や、現在のリベース操作が実行不可能であると判明した場合は、git rebase --abort を使用してリベース操作をキャンセルし、元の状態に戻してから他の解決策を考えることをお勧めします

git rebase --skip を使用するのは推奨されません

git rebasegit merge の使用シーン

一般的に、すべてのブランチの完全なコミット履歴を保持したい場合はマージを使用し、コミット履歴をシンプルかつ直線的に保ちたい場合はリベースを使用します。

具体的には、以下の二つの広く認識されている使用シーンがあります:

  • プライベートブランチや共有されていないブランチ(例:feature-xxxfix-xxxrefactor-xxx など)で開発を行う際、他のブランチの最新コードを取り込む場合、コミット履歴をシンプルかつ直線的に保つために git rebase を使用するのが適しています。

  • プライベートブランチや共有されていないブランチ(例:feature-xxxfix-xxxrefactor-xxx など)を共有ブランチ(例:masterdevelopmain など)にマージする際には、すべてのコミット履歴を記録するために git merge を使用し、コード変更の追跡を容易にするのが適しています。

参考