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履歴の整理(
rebase/rebase --rebase-merges) - push済み履歴で整理を試みるべきか否かの判断
- pullの再発防止設定:
pull.rebase/pull.ff=only - PR merge strategy(Squash/Rebase/Merge commit)の使い分け
- fast-forwardの仕組みと、いつマージコミットを許容するか
なぜpullするとマージコミットが増えるのか
git pullの既定動作は「fetch+merge」。自分のブランチにローカルコミットが1件でもある状態でpullすると、リモートを取り込む時点で分岐が発生し、統合のためのマージコミットが必ず1件生まれます。これをpullの度に繰り返すと、マージコミットが雪だるま式に増えていくというわけです。
# pull = fetch + merge の合成 git pull # ≒ git fetch + git merge origin/main # 自分のcommitと同僚のcommitが分岐 → マージコミット生成 # pullごとに新しい統合点ができる
fast-forwardとマージコミットの違い
# 自分にローカルコミットがない時の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で履歴を直線化するのがもっとも安全かつ簡単です。自分のコミット列だけをリモート先端の上に積み直します。
# 最新情報取得 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ができます。
# 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戦略を改善するのが無難。どうしても整理が必要なら、全員合意+周知徹底が必須です。
# 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の既定動作。設定一行で大きく改善できます。
# 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.ff = onlyが最も安全。「FFできない時は明示的にrebaseかmergeを選ぶ」ワークフローになり、「意図せずマージコミットが増える」が物理的に起きなくなります。チーム全員に設定を共有すれば統一的な履歴スタイルが保てます。
PRマージ戦略を使い分ける
GitHub等のPRマージには3つの方式があり、履歴の見た目とマージコミットの扱いが異なります。チームでどれを使うかを揃えると履歴が安定します。
選び方の指針
- Squash:PR内のwip commitを気にせず作業できる、リリースノート作成も楽
- Rebase:各コミット単位で意味を持たせたい、
bisectしやすい - Merge commit:ブランチの開発履歴を後で追いかけたい
- チームで方針を決め、GitHubの設定で許可する方式を絞るのがおすすめ
実践シナリオ
シナリオ① 1日で3回pullしてマージコミット3件できた
# 現状把握 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マージだけ消す
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 pullはmerge動作のまま。「マージ嫌だからrebase派」ならリポジトリ単位でも設定しておかないと事故の元。git config --global pull.rebase trueを一度設定すれば以降迷わずに済みます。
rebase中のconflict解消ミスでコミットを捨てる
rebase中の--skipは「そのコミットを丸ごと捨てる」オプション。「面倒だから」とスキップすると自分の変更が履歴から消えます。解消に困ったら--abortで中止、解決できるまで--continueで進めましょう。
rebaseしすぎて履歴の意味が失われる
rebaseは「直線化」できる反面、いつ・誰が・なぜ統合したかという文脈情報を消してしまうことがあります。全てsquashすれば履歴は綺麗ですが、後のgit blameやgit bisectで細かい情報が欲しい時に困ることも。バランスが重要です。
GitHubのPR merge strategyがチームで揃っていない
Squash派とMerge commit派が混在すると履歴がジグザグになります。GitHubのリポジトリ設定で許可する方式を限定する(例:Squash onlyにする)とチーム全員で履歴スタイルが統一され、読みやすさが保てます。
よくある質問
pull.rebase=true、「FF不可なら止めて明示的に判断したい」ならpull.ff=only。後者の方が事故を防ぎやすく初心者にも推奨しやすい設定です。git rebase origin/mainで一発直線化。push済みの共有ブランチは触らず、以降の運用を改善するのが無難です。個人ブランチなら--force-with-leaseでpush更新可能。git rebase --abortで一度中止、git merge origin/mainに切り替えるのも選択肢です。mergeならconflict解消が1回で済みます(マージコミットは1件発生)。git pull --rebase origin mainまたはgit rebase origin/mainで本線の変更を取り込み、--force-with-leaseでPRブランチを更新します。GitHubの「Update branch」ボタンはmerge commit方式なので、直線履歴派は手動rebaseを。pull.rebase=merges設定でマージコミットを保持したままrebase可能。pullマージだけ消えて、featureブランチ統合点は残ります。git branch backup/before-rebaseで整理前の位置を保存しておくと確実です。関連記事
- 【Git】rebaseとmergeの違いと使い分け — 統合戦略の基礎
- 【Git】リモートとローカルの履歴が食い違ったときの同期方法 — pull戦略の選び方
- 【Git】pushしようとしたら”non-fast-forward”で拒否されたときの解決方法 — rebase後のpush問題
- 【Git】mergeコミットを取り消して履歴を元に戻す方法 — マージコミット取消
- 【Git】マージの取り消し方法 — merge前への復帰
- 【Git】リベース途中でエラーになったときの復旧方法 — rebase中のトラブル対処
- 【Git】pushを取り消す方法 — push済み変更の対処
- 【Git】よく使うgitコマンドまとめ — 日常コマンドの早見表
まとめ
- 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ログを積み重ねていきましょう。

