はじめに
チーム開発でfeatureブランチを使って作業していると、mainブランチに他のメンバーの変更が入って、自分のブランチが古くなってしまうことがよくあります。こうしたシーンで自分はいつもmainの変更をfeatureにmergeすることで解消していたのですが、同僚から「git rebaseでもできるよ」と教えてもらい、今更ながらrebaseについて調べてみました。
この記事では、mainの最新の変更を自分の作業ブランチに取り込む2つの方法、git rebaseとgit mergeの違いと使い分けについて解説します。
前提状況
# mainブランチから作業ブランチを切って開発中
git checkout -b feature
# 開発中にmainに他の変更が入ってしまった
この状態で、mainの最新の変更を取り込みたい場合、2つの選択肢があります。
方法1: git merge を使う
手順
# mainを最新に更新
git checkout main
git pull origin main
# 作業ブランチに戻ってmerge
git checkout feature
git merge main
履歴のイメージ
main: A---B---C---D
\ \
feature: E---F---G---M (マージコミット)
mainとfeatureの変更を統合する「マージコミットM」が作られます。
メリット
- 直感的に操作できる
- コンフリクト解決は1回だけ
デメリット
- マージコミットが増えて履歴が複雑になる
- 複数人が頻繁にmainをmergeすると、履歴の見通しが悪くなる
方法2: git rebase を使う
手順
# mainを最新に更新
git checkout main
git pull origin main
# 作業ブランチに戻ってrebase
git checkout feature
git rebase main
履歴のイメージ
main: A---B---C---D
\
feature: E'---F'---G'
featureブランチのコミットがmainの最新コミットの上に「付け替え」られます。一直線の履歴になります。
メリット
- 履歴が一直線できれい
- あたかも最新のmainから作業を始めたかのような状態になる
- PRレビュー時に変更が追いやすい
- 不要なマージコミットが増えない
デメリット
- コミットごとにコンフリクト解決が必要な場合がある
- コミットハッシュが変わる(履歴の書き換え)
- 既にpush済みの場合は強制pushが必要
リモートにpush済みの場合の注意
rebase後は、コミット履歴が書き換わるため、強制pushが必要です。
# 安全な強制push
git push origin feature --force-with-lease
--force-with-leaseは、リモートが予期しない変更を受けていないか確認してからpushするので、--forceより安全です。
まとめ
| 観点 | git merge | git rebase |
|---|---|---|
| 履歴 | 枝分かれする | 一直線になる |
| 安全性 | 高い | やや注意が必要 |
| コンフリクト解決 | 1回 | コミットごと |
| 強制push | 不要 | 必要(push済みの場合) |
| 推奨場面 | 最終的なマージ | 作業中の同期 |
感想
調べてみた感想として、複雑なコンフリクトが起きない限りはrebaseの方がいいなあと感じました。コミット履歴が一直線に美しくなるのが良いですね。ただ、rebaseによって厄介なコンフリクトが発生して、その解決に時間を取られるとかがあるならmainをマージする方がいいかも?とも思いました。
※まあ厄介なコンフリクトが起こるような状況なら、rebase/mergeのどちらを選んでもコンフリクト自体は発生しそうですが。