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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?