【Git】push後に別ブランチの変更が混ざってしまったときの対処法

【Git】push後に別ブランチの変更が混ざってしまったときの対処法 Git

「pushしたら、別ブランチの変更まで混ざってしまった」——共同開発では珍しくありません。
原因の多くは、作業ブランチを切り忘れて別ブランチでコミット・pushしてしまった、PR前に誤ってmergeしてしまった、あるいはrebaseやcherry-pickで意図せず取り込んでしまった、のいずれかです。
本記事では、影響を最小化するための診断手順と、公開後でも安全に元に戻す具体的な対処法を整理します。

まずは診断:どのコミットが「混入」なのかを特定する

取り消す対象を明確にするため、最新の参照を取得してから、どのコミットがどちら側で生まれたかを左右付きで可視化します。
同内容かどうかの判定を有効にすると、重複取り込みの見分けがつきやすくなります。

# 参照更新と掃除
git fetch --prune

# 左右付きログ(左: origin/main 側、右: 現在のブランチ側)
git log --oneline --decorate --graph --left-right --cherry origin/main...HEAD

# 直前の操作履歴(rebaseやcherry-pickの痕跡)も確認
git reflog

混入が「単一コミットなのか」「範囲なのか」「マージコミットなのか」を切り分けられたら、以降の手順を選びます。

ケースA:誤って別ブランチ(例:main)に自分の作業をpushしてしまった

もっとも安全なのは、混入コミットを正しいブランチへコピーし、間違って載った側では打ち消しコミットで取り消す方法です。
公開履歴を書き換えないため、他メンバーへの影響が最小です。

# 1) 正しいブランチへ変更を複製(ハッシュは混入コミット群)
git switch -c feature/fix
git cherry-pick <hash1> <hash2> ...

# 2) 間違って載ったブランチで打ち消し
git switch main
git revert <hash1> <hash2> ...
git push origin main

直前にpushしたばかりで、チーム合意のもと履歴をすっきり戻したい場合に限り、混入前の位置へ巻き戻して安全な強制pushを行います。
他者の更新を誤って消さないため、--force-with-leaseを必ず使います。

# 例:混入直前の正しいハッシュを base とする
git switch main
git reset --hard <base>
git push --force-with-lease origin main

ケースB:誤ってmergeしてしまい、相手ブランチの変更が丸ごと混ざった

混入が「マージコミット」一発で起きているなら、親を指定したrevertで安全に取り消せます。
通常は親1がマージ先(今いるブランチ)です。

# マージコミット M を取り消す(親1を基準)
git revert -m 1 <merge-commit-hash M>
git push

取り消し後に必要な変更だけを改めて正しいブランチへcherry-pickすることで、履歴の整合性を保てます。

ケースC:rebase/cherry-pickで一部コミットだけ意図せず混入した

同内容の別ハッシュが混じった場合は、対象コミットを選んでrevertすれば公開履歴を壊さず戻せます。
複数にまたがるときは、まず作業用ブランチを切り、--no-commitでまとめて打ち消した上で一度にコミットすると安全です。

# まとめて打ち消してからコミット
git switch -c fix/revert-mixed
git revert --no-commit <hashA> <hashB> <hashC>
git commit -m "Revert unintended mixed commits"
git push

ケースD:数ファイルだけ混ざった(コミット全体を戻すほどではない)

変更の混入がファイル単位で限定的なら、対象コミットの一つ前(親)から内容を引き戻すと被害を最小化できます。
取り消しコミットよりも差分が小さく、レビューが容易です。

# 例:混入コミット X の直前状態にファイルを戻す
git restore --source <X^> -- path/to/fileA path/to/fileB
git add .
git commit -m "Restore files to state before <X>"
git push

ケースE:すでに多数の混入が広がった、PRやタグにも影響している

影響範囲が大きい場合は、新しい修正用ブランチを切って履歴を整理し、PRで合意形成したうえで安全な強制pushを使います。
手順と影響を明文化し、既存クローン向けの再同期手順を合わせて周知します。

# 修正用ブランチで履歴を整形(必要なら対話的に)
git switch -c fix/history
git rebase --rebase-merges -i origin/main

# レビュー・CI通過後に安全な上書き
git push --force-with-lease origin fix/history:main

# 既存クローン側の再同期例
# (各自のリポジトリで)
# git fetch --prune
# git checkout main
# git reset --hard origin/main

直す前の保険:退避ブランチとreflogの確認

履歴操作に入る前に、現在の先端から退避ブランチを切っておくと、万一の巻き戻しが容易です。
reflogでHEADの移動履歴を掴んでおけば、誤操作からの復元ルートを確保できます。

git switch -c safety/backup-$(date +%Y%m%d-%H%M)
git reflog

再発防止:運用と設定を整える

ブランチ保護とレビュー前提のPR運用に切り替えると、誤pushや誤mergeは大幅に減ります。
日常の同期は、フェッチ後に早送りまたはリベースで直線的に取り込み、不要なマージコミットを抑制します。
参照掃除を有効化して古い追跡ブランチを放置しないこと、作業開始時に必ずブランチ名とupstreamを確認する習慣も効果的です。

# 直線履歴を維持(pullをリベース)
git config --global pull.rebase true

# 早送りのみ許可した取り込み
git config --global pull.ff only

# 参照掃除の自動化
git config --global fetch.prune true

# 現在の追跡先を素早く確認
git rev-parse --abbrev-ref --symbolic-full-name @{u}

まとめ

別ブランチの変更が混ざったときは、まず「混入したコミットの正体」を特定し、公開履歴を壊さずに戻せる手段から選ぶのが基本です。
単発ならrevert、マージなら親指定のrevert、ファイル限定ならrestore、どうしても履歴を戻す必要がある場合のみ合意のうえで--force-with-leaseを使います。
退避ブランチとreflogで退路を確保し、以後はPR・保護ブランチ・直線的な同期を徹底すれば、同種の事故は確実に減らせます。