【Git】コミット履歴が二重に並んでしまったときの原因と修正方法

【Git】コミット履歴が二重に並んでしまったときの原因と修正方法 Git

「コミット履歴が二重に並んで見える」「同じ変更が別のハッシュで繰り返し出てくる」という現象は、単なる見た目の問題ではなく、
履歴の作り方や取り込み方に起因することがほとんどです。典型例は、rebaseやcherry-pickで“同内容だが別ハッシュ”のコミットが生まれ、
その後にmergeで統合してしまうパターンです。本記事では、なぜ二重化が起きるのかを仕組みから整理し、状況別の診断と安全な修正方法を解説します。

なぜ履歴が二重に見えるのか

GitのコミットIDは内容と親をハッシュ化して決まります。rebaseやcherry-pickは「既存の変更を別の親の上に載せ直す」ため、
中身は同じでも新しいコミットIDが生成されます。こうして生まれた“同内容・別ハッシュ”の列を、さらにmergeで取り込むと、
片側に元のコミット、もう片側に再生成されたコミットが並び、グラフ表示では二重に見える状態になります。
また、PRを「Squash and merge」で取り込むと、元の細かいコミットは一つの新コミットに畳み込まれますが、
開発者がローカルで同じ変更列を保持したまま別経路で取り込むと、似た内容が重複して現れます。

まずは診断:重複の正体を見極める

二重化の多くは「同内容かどうか」の見極めで切り分けられます。左右比較と重複マーキングを使って確認します。

# 例:mainと自分のブランチの差分を“同内容重複”つきで可視化
git fetch --prune
git log --oneline --decorate --graph --cherry --left-right origin/main...HEAD

# 同内容判定を強化して比較
git log --cherry-mark --left-right origin/main...HEAD

ローカル操作の履歴から二重化のきっかけを把握したいときはreflogが有効です。直前にrebaseやcherry-pickをしてからmergeしていないかを確認します。

git reflog

ケースA:ローカルだけ二重化している(未push)

まだ共有していないなら、履歴を整えてからpushするのが最も安全です。基本は「片側へ寄せる」方針で直線化します。
すでに元ブランチの先端が正として扱えるなら、自分の作業をそこへrebaseして重複を解消します。

# 例:mainを正として、自分の作業を載せ直す
git fetch --prune
git rebase origin/main

# コンフリクト時は解消→ステージ→続行
git add .
git rebase --continue

# やり直す場合
git rebase --abort

一部のコミットだけが重複している場合は、対話的rebaseで重複コミットをdropまたはsquashします。マージ構造を保ったまま整理したいなら--rebase-mergesが役立ちます。

# マージ構造を意識しつつ整理
git rebase --rebase-merges -i origin/main

ケースB:リモートへ二重履歴をpushしてしまった(共有済み)

共有履歴の書き換えは影響が大きいため、原則はそのまま運用し、以後の履歴を綺麗に保つ選択が安全です。
どうしても整理が必要なら、PRで変更内容を提示し、合意のうえで最小範囲だけを書き換えます。作業は新ブランチ上で行い、完了後に安全な強制pushを行います。

# 共有済み履歴の最小書き換え(要合意)
git fetch --prune
git switch -c fix/flatten-history
git rebase --rebase-merges -i origin/main
# 内容確認後、他者の更新を保護しつつ上書き
git push --force-with-lease -u origin fix/flatten-history

メインブランチ自体を整える必要がある場合でも、まずはPR経由でレビューとCIを通し、影響範囲を明示します。
既存クローンには再同期手順(再クローンまたはfetch --prunereset --hard)を周知します。

ケースC:PRのSquash merge後にローカルで二重化した

Squash mergeで取り込まれた変更は、リモート上では新しい単一コミットになります。ローカルに旧コミット列が残っていると、取り込み時に似た内容が二重化します。
最も簡単なのは、ローカルの作業ブランチを閉じて、最新mainから作り直す方法です。

# mainを更新して作業ブランチを作り直す
git fetch --prune
git switch main
git reset --hard origin/main
git switch -c feature/new-work

既存ブランチで継続する必要があれば、git rebase origin/main後に不要コミットをdropして揃えます。

ケースD:cherry-pick多用で同内容が散在している

過去のブランチからcherry-pickを繰り返すと、同内容のコミットが複数の枝に生まれがちです。修正タイミングで、基準ブランチへ一度rebaseし、
--cherryで重複を確認しつつ、対話的rebaseで統合します。広範囲に及ぶ場合は、一度「取り込み専用ブランチ」を切って整理してからメインへ取り込みます。

直す前に必ずやる保険

履歴操作前に退路を確保しておくと事故を防げます。現在の先端からバックアップブランチを切り、reflogの存在も確認します。

# 退避ブランチを作成
git switch -c safety/backup-$(date +%Y%m%d-%H%M)

# HEADの移動履歴を確認
git reflog

再発防止:取り込み方針をチームで統一する

「日常の同期はrebase」「PRはfast-forward可能ならff-only、それ以外はmerge」など、方針を固定すると二重化は激減します。
pullの既定をrebaseにし、不要なマージコミットを避ける設定を組み合わせます。参照掃除を有効にして古い追跡ブランチの混乱も防ぎます。

# 直線履歴を維持
git config --global pull.rebase true
git config --global pull.ff only
git config --global fetch.prune true

まとめ

履歴が二重に並ぶ主因は、rebaseやcherry-pickで別ハッシュが生まれた後にmergeで統合してしまうことにあります。
未pushならrebaseや対話的rebaseで直線化し、共有済みなら影響を見極めたうえでPR経由の最小書き換えか、以後の運用で綺麗に保つ方針へ切り替えます。
取り込み戦略をチームで統一し、--cherryやreflogで早めに異常に気づける体制を整えれば、二重履歴は確実に減らせます。