- Git
- 技術 💻
git mergeとrebaseの違い|使い分けとコンフリクト解消
git mergeは、2つのブランチの履歴をそのまま残して統合する方法、git rebaseは、自分のコミットを相手のブランチの最新の位置に付け直して、一直線の履歴にする方法です。どちらも「他のブランチの変更を取り込む」ための操作ですが、履歴の残り方が違います。
使い分けの基本は、「共有しているブランチの履歴は書き換えない(mergeを使う)」「自分だけの作業ブランチを最新に追従させるときは、rebaseを使ってもよい」です。この記事では、履歴の違いを図で確認したうえで、使い分けの基準、rebaseの注意点、コンフリクトの解消手順、失敗したときの戻し方までを解説します。
履歴の違いを図で見る
mainブランチから feature ブランチを作って作業している間に、mainにも別の人のコミット(C)が追加された状況を考えます。
A---B feature
/
o---o---C main
git merge の場合
git switch feature
git merge main
A---B---M feature
/ /
o---o-------C main
マージコミット(M)が作られ、2つの履歴が合流します。A・Bのコミットはそのままで、「いつ、どのブランチを取り込んだか」という事実も履歴に残ります。
git rebase の場合
git switch feature
git rebase main
A'--B' feature
/
o---o-----C main
A・Bのコミットが、mainの最新(C)の後ろに付け直されます。履歴は一直線になり、マージコミットはできません。
ここで重要なのは、A'とB'は、AとBとは別のコミットだということです。内容は同じでも、親のコミットが変わるため、コミットID(ハッシュ)が新しくなります。これが、rebaseの注意点のすべての出発点です。
mergeとrebaseの比較
| merge | rebase | |
|---|---|---|
| 履歴の形 | 分岐と合流が残る | 一直線になる |
| 既存のコミット | 変わらない | 作り直される(IDが変わる) |
| マージコミット | できる | できない |
| 共有ブランチでの使用 | 安全 | 原則として避ける |
| コンフリクトの解消 | 1回でまとめて | コミットごとに発生することがある |
| 履歴の読みやすさ | 分岐が多いと追いにくい | 追いやすい |
| 事実の記録 | 「いつ取り込んだか」が残る | 残らない |
使い分けの基準
| 場面 | おすすめ | 理由 |
|---|---|---|
| 自分だけの作業ブランチを、mainの最新に追従させる | rebase(またはmerge) | 履歴が一直線になり、レビューしやすい |
| プルリクエストをmainに取り込む | チームの方針で決める(下記) | ― |
| 他の人も使っているブランチに、変更を取り込む | merge | 他の人の手元の履歴を壊さない |
| push前に、自分のコミットを整理する | rebase -i | 細かなコミットをまとめ、履歴を読みやすくする |
| mainやdevelopなどの共有ブランチそのもの | merge(rebaseしない) | 全員が使っている履歴を書き換えない |
黄金律:push済みの共有ブランチはrebaseしない
rebaseはコミットを作り直すため、すでにpushして他の人が使っているブランチをrebaseすると、他の人の手元の履歴と食い違います。その状態で他の人がpullすると、同じ変更が二重に取り込まれたり、大量のコンフリクトが起きたりします。
「自分しか使っていないブランチ」だけをrebaseする、というのが最も大切なルールです。
rebase後のpushには --force-with-lease
自分の作業ブランチをpush済みの状態でrebaseすると、履歴が変わるため、通常のpushは拒否されます。この場合は、強制的にpushする必要があります。
git push --force-with-lease
--force は、リモートの状態に関係なく上書きします。--force-with-lease は、「自分が最後に取得したときから、リモートが変わっていなければ上書きする」という安全装置付きの強制pushです。レビュー中に誰かがコミットを追加していた場合は、上書きせずにエラーになるため、他の人の変更を消してしまう事故を防げます。
プルリクエストの取り込み方(GitHubの3つの選択肢)
GitHubでは、プルリクエストをマージするときに3つの方法を選べます。
| 方法 | mainの履歴 | 向いているチーム |
|---|---|---|
| Create a merge commit | マージコミットで合流。ブランチのコミットもすべて残る | 作業の経緯を細かく残したい |
| Squash and merge | ブランチのコミットを1つにまとめて追加 | 1プルリクエスト=1コミットで管理したい。途中のコミットの質を問わない |
| Rebase and merge | ブランチのコミットを一直線に付け直して追加 | 一つ一つのコミットを整えて出す文化がある |
どれが正解というより、チームで1つに統一することが大切です。混在すると、履歴の読み方がばらばらになります。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「git mergeとrebaseの違い|使い分けとコンフリクト解消」
git pull と rebase
git pull は、git fetch(リモートの取得)+ git merge を行うコマンドです。自分のローカルにもコミットがある状態でpullすると、そのたびに小さなマージコミットができ、履歴が複雑になります。
git pull --rebase # 今回だけrebaseで取り込む
git config --global pull.rebase true # 常にrebaseで取り込む
git config --global pull.ff only # fast-forwardできるときだけ取り込む
pull --rebase は、まだpushしていない自分のコミットを、リモートの最新の後ろに付け直します。自分のコミットはまだ誰とも共有していないので、黄金律にも反しません。
fast-forwardマージ
取り込む側のブランチに新しいコミットがない場合、mergeはマージコミットを作らず、ブランチの位置を進めるだけで済ませます。これをfast-forward(早送り)と呼びます。
git merge --no-ff feature # fast-forwardできる場合でも、マージコミットを作る
「どの機能がいつ取り込まれたか」を履歴に残したい場合は、--no-ff を使います。
コンフリクトの解消手順
同じファイルの同じ部分を、両方のブランチで変更していると、コンフリクト(競合)が起きます。Gitは自動で判断できないため、人が内容を決めます。
コンフリクトの表示
<<<<<<< HEAD
const TAX_RATE = 0.10;
=======
const TAX_RATE = getTaxRate();
>>>>>>> feature
<<<<<<<から=======まで:一方の変更=======から>>>>>>>まで:もう一方の変更
どちらかを選ぶか、両方を組み合わせて正しい内容に書き換え、3つの記号の行をすべて削除します。
mergeの場合
git merge main
# CONFLICT (content): Merge conflict in src/tax.js
# ① ファイルを編集してコンフリクトを解消する
git status # コンフリクトしているファイルを確認
git add src/tax.js # ② 解消したファイルを登録
git commit # ③ マージコミットを作成して完了
git merge --abort # やめて、merge前の状態に戻す
rebaseの場合
git rebase main
# CONFLICT (content): Merge conflict in src/tax.js
# Could not apply 8a90401... 税率の取得を関数化
# ① ファイルを編集してコンフリクトを解消する
git add src/tax.js # ② 解消したファイルを登録
git rebase --continue # ③ 次のコミットへ進む(commit は不要)
git rebase --skip # このコミットを取り込まずに先へ進む
git rebase --abort # やめて、rebase前の状態に戻す
rebaseはコミットを1つずつ付け直すため、付け直すコミットが多いと、同じようなコンフリクトを何度も解消することがあります。作業ブランチを長く放置せず、こまめにmainに追従させると、コンフリクトを小さく保てます。
注意:rebase中は「ours」と「theirs」が逆になる
コンフリクトを「どちらか一方の内容で解決する」コマンドには、--ours(自分側)と --theirs(相手側)があります。ところが、rebase中はこの意味が直感と逆になります。
| 操作 | --ours |
--theirs |
|---|---|---|
feature で git merge main |
feature(自分の変更) | main |
feature で git rebase main |
main(付け直す先) | feature(自分の変更) |
git checkout --theirs src/tax.js # rebase中なら「自分の変更」を採用する
rebaseは、「mainの最新を土台(ours)にして、自分のコミット(theirs)を1つずつ適用する」処理だからです。一方を丸ごと採用するコマンドを使うときは、結果のファイルの中身を必ず確認してから git add します。
同じコンフリクトを何度も解消しないために
git config --global rerere.enabled true
rerere(reuse recorded resolution)を有効にすると、一度解消したコンフリクトの解決方法を記録し、同じコンフリクトが起きたときに自動で適用します。rebaseで同じ箇所のコンフリクトが繰り返される場合に便利です。
rebase -i でコミットを整理する
push前に、細かなコミットをまとめたり、メッセージを直したりするには、対話的rebaseを使います。
git rebase -i main # mainから分岐した後のコミットを整理する
エディタに、次のような一覧が表示されます。
pick a1b2c3d 注文一覧の画面を追加
pick e4f5a6b typo修正
pick 7c8d9e0 レビュー指摘対応
先頭の単語を書き換えて保存すると、その通りに処理されます。
| コマンド | 意味 |
|---|---|
| pick | そのまま使う |
| reword | コミットメッセージを書き直す |
| squash | 直前のコミットにまとめる(メッセージも結合して編集) |
| fixup | 直前のコミットにまとめる(このコミットのメッセージは捨てる) |
| drop | コミットを削除する |
git commit --fixup=<コミットID> で修正用のコミットを作っておき、git rebase -i --autosquash main を実行すると、fixupの並べ替えを自動で行ってくれます。
失敗したときの戻し方
rebaseで履歴がおかしくなっても、元のコミットはすぐには消えません。git reflog で、rebase前の位置を探して戻せます。
git reflog
# e4f5a6b HEAD@{0}: rebase (finish): returning to refs/heads/feature
# ...
# 9f8e7d6 HEAD@{5}: commit: レビュー指摘対応 ← rebase前の最後のコミット
git reset --hard HEAD@{5} # rebase前の状態に戻す
reset --hard は、コミットしていない変更を消すので、実行前に git status で確認してください。rebaseを始める前に、git branch backup-feature のように、バックアップ用のブランチを作っておくと、さらに安心です。
よくある質問
Q. 結局、mergeとrebaseのどちらを使えばいいですか? A. 共有ブランチへの取り込みはmerge(またはGitHubのマージボタン)、自分だけの作業ブランチの整理や最新への追従はrebase、という使い分けが一般的です。チームのルールがあれば、それに従います。
Q. 初心者はrebaseを使わない方がいいですか?
A. mergeだけでも開発はできます。まずは git pull --rebase や、push前の git rebase -i のように、自分だけのコミットを扱う場面から使い始めると安全です。
Q. 間違えてmainをrebaseしてpushしてしまいました。
A. 他のメンバーに作業を止めてもらい、git reflog で元の位置を確認して戻します。すでに他の人がpullしている場合は、どの状態に揃えるかをチームで決めてから対応します。mainには、強制pushを禁止する保護設定(ブランチ保護ルール)をかけておくと、この事故を防げます。
Q. rebaseすると、コミットの日時は変わりますか? A. 作者の日時(AuthorDate)は元のまま残り、コミットした日時(CommitDate)がrebaseした時刻に変わります。
まとめ
- mergeは履歴を残して合流、rebaseはコミットを付け直して一直線にする
- rebaseはコミットIDが変わる。push済みの共有ブランチはrebaseしない
- 自分のブランチをrebaseした後のpushは
--force-with-lease git pull --rebaseで、pull時の余計なマージコミットを防げる- コンフリクトは、記号を消して正しい内容にし、add →(mergeならcommit/rebaseならcontinue)
- rebase中は ours と theirs が逆。迷ったら
--abortで戻る - 失敗しても
git reflogで戻せる。事前にバックアップのブランチを作ると安心
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「git mergeとrebaseの違い|使い分けとコンフリクト解消」
株式会社RJCでは、Gitのようなチーム開発の基本を、実際に手を動かしながら仲間と教え合っています。こうした知識を共有しながら、一緒に成長を楽しんでいます。
株式会社RJC ― SI事業・SES事業・AI駆動開発。未経験からエンジニアに育てるのが得意な会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。