【Git】pull後にマージコミットが大量発生する原因と履歴整理方法|rebase・pull戦略・PR merge strategy完全ガイド

【Git】pull後に大量のマージコミットが発生したときの原因と履歴整理方法 Git

git log --graphを眺めたら「Merge branch ‘main’ of github.com/…」の行が延々と並んでいる——作業内容は大したことないのに、pullするたびにマージコミットが増え、本当の変更がノイズに埋もれて見えなくなる現象です。原因はgit pullの既定動作(fetch+merge)。ローカルに自分のコミットがある状態でpullすると、毎回マージコミットが1件生まれてしまいます。

積み上がったマージコミットの例
* 2a3b4c5 fix: 細かい修正
*   Merge branch 'main' of github.com:user/repo
|\
| * 1a2b3c4 feat: 同僚のコミット
* | 7f8e9d0 fix: 自分の作業
|/
*   Merge branch 'main' of github.com:user/repo   ← 3回前の pull
|\
* |   Merge branch 'main' of github.com:user/repo   ← 5回前の pull

この記事では、pull後にマージコミットが大量発生するメカニズム既に膨らんだ履歴の整理方法再発を防ぐpull戦略、そしてPRのマージ戦略(Squash/Rebase/Merge commit)まで実務で役立つ形で整理します。

この記事で学べること

  • pull後にマージコミットが発生する仕組み(fetch+mergeの副作用)
  • git log --merges --graphによる現状診断
  • 未push履歴の整理(rebaserebase --rebase-merges
  • push済み履歴で整理を試みるべきか否かの判断
  • pullの再発防止設定:pull.rebasepull.ff=only
  • PR merge strategy(Squash/Rebase/Merge commit)の使い分け
  • fast-forwardの仕組みと、いつマージコミットを許容するか
スポンサーリンク

なぜpullするとマージコミットが増えるのか

git pullの既定動作は「fetch+merge」。自分のブランチにローカルコミットが1件でもある状態でpullすると、リモートを取り込む時点で分岐が発生し、統合のためのマージコミットが必ず1件生まれます。これをpullの度に繰り返すと、マージコミットが雪だるま式に増えていくというわけです。

fetch+merge の動き
# pull = fetch + merge の合成
git pull             # ≒ git fetch + git merge origin/main

# 自分のcommitと同僚のcommitが分岐 → マージコミット生成
# pullごとに新しい統合点ができる

fast-forwardとマージコミットの違い

FF可能な場合はマージコミット不要
# 自分にローカルコミットがない時のpull
# リモートA-B-C → ローカル空 → FFで早送りのみ、マージコミット生成されず

# 自分にローカルコミットがある時のpull
# リモートA-B-C-X(同僚push)
# ローカルA-B-C-Y(自分commit)
# → 分岐しているのでmerge必須 → Merge commit生成

ポイント:マージコミットが悪いわけではなく、「頻繁なpullで無意味に増える」のが問題。意味のあるマージ(機能ブランチの統合点)なら残す価値があります。問題はpullのたびに生成される意味のないマージコミットで、これをpull.rebase=trueなどで抑止すれば履歴は読みやすくなります。

STEP 0:マージコミットの状況を診断する

現状把握コマンド
# 最新情報取得
git fetch --prune

# グラフ表示で分岐構造を俯瞰
git log --oneline --graph --decorate --all -50

# マージコミットだけ抽出
git log --merges --oneline -30

# pull由来マージかの判別(メッセージ検索)
git log --merges --oneline | grep -E "Merge branch.*of "
# Merge branch 'main' of github.com:... が連続するなら要整理

# 自分のブランチ内のマージ件数を数える
git log --merges --oneline origin/main..HEAD | wc -l

# 整理対象(未push)範囲を確認
git log --oneline origin/main..HEAD

診断ポイント

  • “Merge branch X of github.com”が連続 → pullで増殖している兆候
  • 意味のあるマージ(featureブランチ統合)は残す判断も
  • 未push範囲は自由に整理可、push済みは慎重判断
  • グラフで「ジグザグ」が出るほど統合点が多い履歴

整理①:未pushのマージコミットをrebaseで直線化

まだリモートに公開していない作業ブランチであれば、git rebaseで履歴を直線化するのがもっとも安全かつ簡単です。自分のコミット列だけをリモート先端の上に積み直します。

rebaseで直線化
# 最新情報取得
git fetch --prune

# origin/main の上に自分のcommitを並べ直す
git rebase origin/main

# conflictが出たら解消→continue
git add .
git rebase --continue

# 中止するなら
git rebase --abort

# 未ステージ変更があれば自動退避
git config --global rebase.autoStash true

rebase で起きること

  • 自分のコミットのSHAが全部変わる(履歴書き換え)
  • pullで生まれたマージコミットは消滅
  • 結果として直線的な履歴になる
  • コンフリクトが複数コミット分出ることがある(ひとつずつ解消)

注意:rebase後はSHAが変わるので、push済みなら--force-with-leaseが必要。共有ブランチで自分以外のメンバーも作業していた場合、相手の作業が消える事故につながるため、個人ブランチ限定で使いましょう。

整理②:マージコミットを保ったまま直線化(–rebase-merges)

「意味のあるマージは残したいけど、pull由来のマージコミットは消したい」「複雑な履歴を対話的に整理したい」場合はgit rebase --rebase-merges -iを使います。マージ構造を維持したまま並べ替え・squashができます。

インタラクティブrebase with merges
# origin/main からの自分の作業全体を対話的に整理
git rebase --rebase-merges -i origin/main

# エディタが開く(主な操作):
# pick a1b2c3d feat: xxx            ← 残す
# squash 2a3b4c5 fix: typo          ← 直前のcommitに統合
# drop 3b4c5d6 Merge branch main    ← pullマージを削除
# reword 4c5d6e7 refactor: xxx      ← メッセージ修正

# 保存して閉じると順番に処理される
# conflictが出たら通常通り解消→continue

ポイント:--rebase-mergesマージコミット保持版のrebase。featureブランチの統合点など意味のあるマージは維持し、pull由来の無意味なマージだけdropすれば、読みやすい履歴と統合履歴の両立ができます。

rebase全般のトラブル対処はリベース途中でエラーになったときの復旧方法、rebase vs mergeの選択はrebaseとmergeの違いと使い分けを参照。

整理③:push済みの履歴を整理するか否か

既にリモートに公開された履歴を書き換えるのは慎重な判断が必要です。原則はそのまま運用し、以後のpull戦略を改善するのが無難。どうしても整理が必要なら、全員合意+周知徹底が必須です。

push済み整理の流れ(例外運用)
# 1. チーム全員に事前通知(作業退避を依頼)
# 2. 整理作業
git rebase --rebase-merges -i origin/main

# 3. 安全な強制push
git fetch origin
git push --force-with-lease

# 4. 他メンバーは再同期
git fetch origin
git reset --hard origin/main

警告:共有ブランチ(main/develop)の履歴書き換えは他メンバーのローカルを壊し、CIやwebhookに影響し、タグやリリースの整合性も崩れます。mainへのforce pushは原則避け、「今後の履歴を綺麗に保つ」方向で切り替えるのが健全です。push済みの整理についてはpushを取り消す方法も参照。

再発防止:pull戦略を変える

マージコミット増殖の根本原因はgit pullの既定動作。設定一行で大きく改善できます。

推奨Git設定
# pullでrebaseを優先(直線履歴派の定番)
git config --global pull.rebase true

# FFできる場合のみpullを許可(マージコミット発生を物理的に防ぐ)
git config --global pull.ff only

# rebase時の未ステージ変更を自動退避
git config --global rebase.autoStash true

# 追跡情報のprune自動化
git config --global fetch.prune true

# 初回pushでupstream自動設定(Git 2.37+)
git config --global push.autoSetupRemote true
設定値 pullの挙動 特徴
pull.rebase = true fetch+rebase 直線化、マージコミット発生せず
pull.rebase = merges fetch+rebase(マージ保持) 意味のあるマージは維持
pull.ff = only FF可能時のみpull、不可なら失敗 安全最優先、明示的選択を促す
既定(未設定) fetch+merge マージコミットが増えやすい

おすすめ:個人的にはpull.ff = onlyが最も安全。「FFできない時は明示的にrebaseかmergeを選ぶ」ワークフローになり、「意図せずマージコミットが増える」が物理的に起きなくなります。チーム全員に設定を共有すれば統一的な履歴スタイルが保てます。

PRマージ戦略を使い分ける

GitHub等のPRマージには3つの方式があり、履歴の見た目マージコミットの扱いが異なります。チームでどれを使うかを揃えると履歴が安定します。

方式 履歴の形 向いているチーム
Merge commit 各featureブランチの統合点が残る ブランチ単位の履歴を残したい
Squash and merge PRごとに1コミットに圧縮 フラットで読みやすい履歴重視
Rebase and merge PR内のcommitを並べて直線化 コミット単位のバランスを保ちたい

選び方の指針

  • Squash:PR内のwip commitを気にせず作業できる、リリースノート作成も楽
  • Rebase:各コミット単位で意味を持たせたい、bisectしやすい
  • Merge commit:ブランチの開発履歴を後で追いかけたい
  • チームで方針を決め、GitHubの設定で許可する方式を絞るのがおすすめ

実践シナリオ

シナリオ① 1日で3回pullしてマージコミット3件できた

未push履歴を整理
# 現状把握
git log --oneline --graph --all -10
git log --merges --oneline origin/main..HEAD

# rebaseで直線化
git fetch --prune
git rebase origin/main

# 整理後push
git push

シナリオ② rebaseを今後のpullで自動化

設定変更で恒久対策
git config --global pull.rebase true
git config --global rebase.autoStash true

# 以降 git pull するとrebaseに
git pull

シナリオ③ 意味のあるmergeは残してpullマージだけ消す

対話的rebaseで選別
git rebase --rebase-merges -i origin/main
# エディタで "Merge branch main of ..." 行だけ drop
# 機能ブランチ統合のmergeはpickのまま
# 保存→閉じる

シナリオ④ push済み。もう整理は諦めて以降綺麗にする

運用改善方針
# 過去は触らない
# 以降の設定変更
git config --global pull.ff only

# PRは Squash merge に統一(GitHubリポ設定)
# チームに周知:pull時は明示的にrebase or merge を選ぶ

やってはいけない落とし穴

共有ブランチでrebase→force push

main/develop等の共有ブランチをrebaseしてforce pushすると、他メンバーのローカルが壊れます。pull時にconflictが起き、作業が消える事故も。共有ブランチの過去履歴は触らず、今後の運用改善で対処しましょう。

pull.rebase を設定せずに毎回手動で考える

設定でデフォルトを変えない限り、git pullmerge動作のまま。「マージ嫌だからrebase派」ならリポジトリ単位でも設定しておかないと事故の元。git config --global pull.rebase trueを一度設定すれば以降迷わずに済みます。

rebase中のconflict解消ミスでコミットを捨てる

rebase中の--skipは「そのコミットを丸ごと捨てる」オプション。「面倒だから」とスキップすると自分の変更が履歴から消えます。解消に困ったら--abortで中止、解決できるまで--continueで進めましょう。

rebaseしすぎて履歴の意味が失われる

rebaseは「直線化」できる反面、いつ・誰が・なぜ統合したかという文脈情報を消してしまうことがあります。全てsquashすれば履歴は綺麗ですが、後のgit blamegit bisectで細かい情報が欲しい時に困ることも。バランスが重要です。

GitHubのPR merge strategyがチームで揃っていない

Squash派とMerge commit派が混在すると履歴がジグザグになります。GitHubのリポジトリ設定で許可する方式を限定する(例:Squash onlyにする)とチーム全員で履歴スタイルが統一され、読みやすさが保てます。

よくある質問

Qpull.rebaseとpull.ff=onlyどっち?
A「自分のコミットがある時も自動でrebaseしたい」ならpull.rebase=true、「FF不可なら止めて明示的に判断したい」ならpull.ff=only。後者の方が事故を防ぎやすく初心者にも推奨しやすい設定です。
Q既にある大量マージを全部消したい
A未pushならgit rebase origin/mainで一発直線化。push済みの共有ブランチは触らず、以降の運用を改善するのが無難です。個人ブランチなら--force-with-leaseでpush更新可能。
QSquash merge と Rebase merge どちらを選ぶべき?
Aフラットで読みやすい履歴優先ならSquash、コミット単位の細かい粒度を維持したいならRebase。多くのチームは「PRは1トピック=1機能」の前提でSquashを採用する傾向です。
Qrebase中にconflict解消が難しい
Agit rebase --abortで一度中止、git merge origin/mainに切り替えるのも選択肢です。mergeならconflict解消が1回で済みます(マージコミットは1件発生)。
QPR レビュー中に本線が進んだ時の追従方法
Agit pull --rebase origin mainまたはgit rebase origin/mainで本線の変更を取り込み、--force-with-leaseでPRブランチを更新します。GitHubの「Update branch」ボタンはmerge commit方式なので、直線履歴派は手動rebaseを。
Q意味のあるマージコミット(featureブランチ統合)は残したい
Apull.rebase=merges設定でマージコミットを保持したままrebase可能。pullマージだけ消えて、featureブランチ統合点は残ります。
Q履歴整理中にreflogが溢れて古いコミットが消えないか不安
Areflogは既定90日保持されるので、整理後すぐなら古い履歴に戻せます。不安ならgit branch backup/before-rebaseで整理前の位置を保存しておくと確実です。

関連記事

まとめ

  • pull後マージコミット増殖の原因はpullのデフォルト動作(fetch+merge)
  • 診断はgit log --merges --graphで分岐構造を把握
  • 未pushならgit rebase origin/mainで直線化が最短
  • 意味のあるmergeを残すならrebase --rebase-merges -i
  • push済み共有ブランチは原則そのまま、今後の運用を改善
  • 再発防止:pull.rebase=trueまたはpull.ff=onlyを設定
  • PR merge strategy(Squash/Rebase/Merge commit)はチームで統一

マージコミット増殖は、Gitの既定動作を理解すれば予防も整理も難しくありません。「pullするとmergeコミットが生まれる仕組み」を知り、pull.rebaseまたはpull.ff=onlyを設定するだけで今後の履歴はぐっと綺麗になります。既に溜まったものは未pushならrebaseで直線化、push済みなら諦めて以降の運用改善で対処するのが現実的。チーム全体でPR merge strategyを揃え、読みやすいGitログを積み重ねていきましょう。