【Git】push後に別ブランチの変更が混ざってしまったときの対処法|5パターン診断・revert・救出運用完全ガイド

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

「pushしたら別ブランチ向けのコミットまで入ってしまった」「他人のfeatureブランチの変更が自分のPRに混入している」——共同開発では避けられない事故です。原因はさまざまですが、公開済みの履歴を安全に整えるには手順を間違えないことが重要。焦ってforce pushすると他メンバーの作業まで巻き添えになります。

典型的な混入状況
# 自分のfeature/Aに、誤って feature/B のコミットが混ざった例
* 9f8e7d6 feat(A): 目的の変更
* abc1234 feat(B): 他ブランチのコミット   ← 混入
* def5678 feat(B): 他ブランチのコミット   ← 混入
* 1a2b3c4 feat(A): 目的の変更

この記事では、push後に別ブランチの変更が混ざった場合の診断方法5つの対処パターンを整理します。原則としてgit revertgit restoreで公開履歴を壊さずに戻し、どうしても必要な場合のみ--force-with-leaseで最小限に書き換えます。再発防止のためのbranch protection/pull戦略まで一貫してまとめました。

この記事で学べること

  • 混入コミットの種類(単発/merge/range/ファイル部分)を見極める方法
  • git log --left-right --cherryによる影響範囲の特定
  • 5パターンの対処:誤push/誤merge/rebase混入/ファイル混入/広範混入
  • revert(履歴保持)とreset --hard + force-with-lease(履歴書き換え)の使い分け
  • マージコミットの打ち消し(revert -m 1
  • safety branchとreflogによる退路確保
  • branch protection/PR運用/pull戦略による再発防止
スポンサーリンク

混入が起きる5つの典型パターン

パターン 典型的な経緯 推奨対処
① 別ブランチに誤push ブランチ切替忘れでcommit+push cherry-pick救出+revert
② 誤merge mergeコマンドで取り込み先を間違えた revert -m 1
③ rebase/cherry-pickで一部混入 rebaseで別branchのコミットが紛れた 対象コミットを選んでrevert
④ ファイル単位の混入 コミットの一部だけ不要 git restore --source
⑤ 広範に混入(複数PR/タグ波及) 事後発覚で影響範囲大 新branchで整理+合意後force-with-lease

ポイント:判断基準は「影響規模」と「履歴保持の必要性」。少数commitならrevertで履歴を残す、広範混入や履歴整理が必要なら新ブランチでまとめてから--force-with-lease、という使い分けが鉄則です。

STEP 0:混入コミットを正確に特定する

診断コマンド
# 最新化
git fetch --prune

# 左右比較で混入を可視化
git log --oneline --decorate --graph --left-right --cherry-mark origin/main...HEAD
# < :origin/main 側固有
# > :HEAD 側固有(混入候補)
# = :両方にある(重複)

# どのブランチ由来かも確認
git log --oneline --all --source -30

# 特定コミットがどのブランチで生まれたか
git branch --contains <SHA>

# 直前の操作履歴(rebase/cherry-pick/mergeの痕跡)
git reflog -30

# ファイル単位で見たい場合
git log --follow -p -- path/to/file

診断でまず確認すべきこと

  • 混入は単発範囲マージ
  • 混入元のブランチ名(branch --containsで特定)
  • 混入がpush済みで他メンバーがpullした可能性
  • 影響を受けるPR/CI/タグ/リリースの有無

STEP 1:作業前の保険(safety branch)

履歴操作の前に必ず退避ブランチを切ります。万一の誤操作でもすぐ戻せるようにしておきましょう。

退避とreflog確認
# 現在の先端を安全ブランチとして固定
git switch -c safety/backup-$(date +%Y%m%d-%H%M)

# 元ブランチに戻る
git switch <元のブランチ>

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

ポイント:safety branchを切る癖を付けておけば、操作ミスで履歴が壊れてもgit reset --hard safety/backup-xxxで一発復帰できます。「どうせ元に戻せる」安心感が、複雑な履歴操作でのミスを減らします。

ケース①:別ブランチへ誤pushした

「ブランチ切替を忘れてmainで作業+push」パターン。最も安全な対処は混入コミットを正しいブランチに救出→誤push先でrevert。公開履歴を壊さないので他メンバーへの影響を最小化できます。

救出+revertで安全に戻す
# 1. 正しいブランチへ変更を救出(cherry-pick)
git switch -c feature/rescue
git cherry-pick <hash1> <hash2>
git push -u origin feature/rescue

# 2. 誤push先で打ち消し(履歴を残す)
git switch main
git pull origin main
git revert <hash1> <hash2>
git push origin main

# 3. 正しいブランチからPR作成

誰もpullしていない直後なら reset + force-with-lease も選択肢

直前pushならforce-with-leaseで巻き戻し
# 混入直前の正しいSHAを確認
git log --oneline origin/main -5

# そのSHAに巻き戻す
git switch main
git reset --hard <base_SHA>

# 安全な強制push(他人の間接pushを保護)
git push --force-with-lease origin main

警告:main/developなど保護ブランチへのforce pushは禁止のことが多く、他メンバーが既にpullしていた場合は作業を破壊します。チーム合意+pullしてないことの確認がない限り避け、前述の「救出+revert」を選ぶのが安全。詳細はpushを取り消す方法も参照。

作業ブランチ選択ミス全般は間違えて別ブランチで作業したときの復旧方法で詳しく解説しています。

ケース②:誤merge(マージコミット一発で丸ごと混入)

git merge feature/Bmainに対して実行してしまった」など、マージコミット1件で相手ブランチの全変更が入った場合。マージコミット用のrevert -m 1で安全に打ち消せます。

マージコミット打ち消し
# マージコミットのSHAを確認
git log --merges --oneline -5
# 例: 9f8e7d6 Merge branch 'feature/B' into main

# 親番号を確認(通常は親1=main側が基準)
git show --no-patch --pretty=raw 9f8e7d6

# -m 1 で main側を基準に打ち消し
git revert -m 1 9f8e7d6

# conflict解消
git add .
git revert --continue

git push origin main

-m の番号の意味

  • -m 1:親1(通常main側)を基準 → main状態に戻す(通常これ)
  • -m 2:親2(取り込んだ側)を基準 → featureに寄せる(稀)
  • 親順の確認はgit show --pretty=raw <マージSHA>parent行で

マージコミット全般の取り消しはmergeコミットを取り消して履歴を元に戻す方法マージの取り消し方法も参照してください。

revert後の再マージ問題に注意
# revertで打ち消したfeatureを後で再取り込みしたい場合
# 単にmergeしても取り込まれない(既にrevertされた履歴があるため)

# 対処法①:revertコミット自体をさらにrevert
git revert <revert_commit_SHA>

# 対処法②:feature側に新コミットを足してから再マージ

ケース③:rebase/cherry-pickで一部コミットだけ混入

「mainへrebaseした際に意図しないコミットが拾われた」「cherry-pickの範囲指定を間違えた」のように、一部コミットだけ混入している場合はその対象を狙い撃ちでrevertします。

対象コミットを選んで打ち消し
# 混入コミットをリスト化
git log --oneline --cherry-mark --left-right origin/main...HEAD

# まとめて打ち消すブランチを作る
git switch -c fix/revert-mixed

# --no-commit でまとめて戻し、1件のcommitにする
git revert --no-commit <hashA> <hashB> <hashC>
git commit -m "revert: 誤って混入した3コミットを打ち消し"

# PRを作って合意後マージ
git push -u origin fix/revert-mixed

注意:rebase/cherry-pick由来の混入は同内容で別SHAになっていることが多いため、単純にgit resetでは両方のSHAを消せません。revertで元ブランチ側の対象SHAを選ぶか、複数のrevertが必要になる場合があります。詳細はコミット履歴が二重化する原因と修正方法を参照。

ケース④:ファイル単位で混入(コミット全体を戻すのは過剰)

「コミットの中で数ファイルだけ不要」「意図しない設定ファイルが変更された」のようにファイル単位の混入なら、対象ファイルだけを親コミットの状態に戻すのがきれいです。

特定ファイルだけ親の状態に戻す
# 混入コミット X の親(X^)からファイルを取り出す
git restore --source <X^> -- path/to/fileA path/to/fileB
# 旧コマンド
git checkout <X^> -- path/to/fileA path/to/fileB

# 確認
git status
git diff --staged

# commit&push
git add .
git commit -m "revert: 混入したファイルを親コミットの状態に戻す"
git push

ポイント:この方法はgit revert <X>より差分が小さく、レビューしやすいのが利点。ただし「混入コミットの一部だけ打ち消す」扱いなので、コミットメッセージで何を戻したか明記して後で追跡可能にしましょう。

ファイル復元全般は消したファイルを戻す方法も参考になります。

ケース⑤:広範に混入(PR/タグ/複数メンバーに波及)

「PRレビュー中に大量の混入commitが発覚」「リリースタグ付近まで波及」のように影響が広範な場合は、整理用ブランチで一括整理してからチーム合意のうえで--force-with-leaseで上書き、という流れが現実的。

整理ブランチで一括対応
# 影響範囲を把握
git log --oneline --cherry-mark --left-right origin/main...HEAD

# 整理用ブランチで作業
git switch -c fix/history

# 対話的rebaseで混入コミットをdrop
git rebase --rebase-merges -i origin/main
# エディタで不要commitを drop / squash / reword

# レビュー用にpush
git push -u origin fix/history

# PRで変更内容を提示、合意後にmainへ反映
# (主要メンバーに影響説明+再同期手順を周知)
git switch main
git fetch origin
git reset --hard fix/history
git push --force-with-lease origin main

警告:共有mainへのforce pushは他メンバー全員の再同期が必要になります。事前通知+再clone手順の案内+CI/webhook/タグへの影響確認を徹底。保護ブランチ設定でforce pushが禁止されているケースも多く、どうしても必要な場合のみ管理者経由で一時解除→実施→再有効化の運用が無難です。

再発防止:運用と設定で事故を減らす

運用ルールのベストプラクティス

  • Branch protection rulesでmain/developへの直接pushを禁止
  • PRベースのマージ:レビュー+CIパスを必須にする
  • PR mergeはSquash統一で履歴をクリーンに
  • commit前にgit branch --show-currentで作業ブランチ確認
  • pullは--rebaseを既定にして不要マージコミットを抑止
  • 長命ブランチは定期的にmainをrebaseして衝突リスク低減
推奨設定
# pullをrebaseに
git config --global pull.rebase true

# FF以外のpullを止めて慎重にする派
git config --global pull.ff only

# 参照掃除
git config --global fetch.prune true

# 初回push時の自動upstream(Git 2.37+)
git config --global push.autoSetupRemote true

# 強制pushはforce-with-leaseをデフォルトに
git config --global alias.fp "push --force-with-lease"

# 現在のブランチを派手に表示(prompt設定)
# → PS1にgitブランチを含めるなど

実践シナリオ

シナリオ① mainに自分のfeature commitを誤push

救出+revert
# 救出ブランチ
git switch -c feature/rescue main
git cherry-pick <feature_SHA1> <feature_SHA2>
git push -u origin feature/rescue

# mainで打ち消し
git switch main
git pull origin main
git revert <feature_SHA1> <feature_SHA2>
git push origin main

シナリオ② 誤merge直後

revert -m 1 で打ち消し
git revert -m 1 <マージSHA>
git push
# 再度featureを取り込む時はrevert of revertで

シナリオ③ 1ファイルだけ混入

ファイル単位で戻す
git restore --source <混入SHA>^ -- path/to/file
git add path/to/file
git commit -m "revert: fileを混入前の状態に戻す"
git push

シナリオ④ PR大量にcommit混入

整理用ブランチで対応
git switch -c fix/history
git rebase --rebase-merges -i origin/main
# 不要commit drop → push → PR
git push -u origin fix/history

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

共有ブランチでreset –hard+force push

mainの混入を消すためにreset --hardforce pushすると、他メンバーがpullしていた場合にローカルを破壊します。共有ブランチはrevertで打ち消しコミット追加が原則。forceは個人ブランチ限定で--force-with-leaseを。

safety branchを切らずに直接rebase

対話的rebaseでdrop操作を誤ると重要なcommitを失います。git branch safety/backupで退避しておけば、いつでもreset --hard safety/backupで復帰可能。時間コストは1秒、得られる安心感は絶大です。

revert of revertを知らずに再マージで困る

一度revertした機能ブランチを後で再取り込みしたいとき、単純にmergeすると既に取り込まれた扱いで差分が反映されません。「revertをさらにrevert」または「feature側に新commit追加」で対処。

ファイル単位で戻すべきところをコミット単位でrevert

「ファイル2つだけ混入」なのにgit revert <commit>でコミット全体を打ち消すと、必要な変更まで戻ってしまいます。影響を受けるファイルが明確ならgit restore --sourceで限定的に戻すほうがレビューも楽で事故が減ります。

チーム通知を怠ってforce push

たとえ--force-with-leaseでも、共有ブランチを書き換えたら他メンバーへの周知は必須。再同期コマンド(fetchreset --hard)をSlackなどで具体的に共有しましょう。

よくある質問

Qrevertとreset、どちらを使うべき?
A原則はrevert(履歴保持)、個人ブランチで直前push直後かつpullされてないならreset+force-with-leaseも選択肢。共有ブランチは必ずrevert
Q混入したのがマージコミットだった
Agit revert -m 1 <マージSHA>で親1(取り込み先)を基準に打ち消し。親2を基準にしたい特殊ケースは-m 2です。
Qrevert後に同じfeatureを再マージしたい
A「revert of revert」で打ち消しを打ち消すか、feature側に新commitを加えてからmerge。単純にmergeしても「既に取り込まれた扱い」で反映されないので注意。
Q複数のコミットを一度に打ち消したい
Agit revert --no-commit <SHA1> <SHA2> <SHA3>でまとめて適用し、最後にgit commitで1コミットにまとめるとレビューしやすい形になります。
Qforce-with-lease と force の違いは?
A--forceは無条件上書き、--force-with-leaseは「ローカルのorigin/xxx認識と一致する時だけ上書き」する安全装置。他人がpushしていた場合は拒否されるため、作業喪失事故を防げます。
Q混入にすぐ気づけなかった場合
APRレビュー時やCIの結果で気づくケースも多い。発覚時点の影響範囲を確認し、広ければケース⑤の整理フロー、狭ければケース③のrevertで対処します。
Qそもそも混入を防ぐには?
Abranch protection/PRベースマージ/pull.rebase=true/commit前ブランチ確認の習慣。ターミナルプロンプトにgitブランチ名を表示する設定もおすすめです。

関連記事

まとめ

  • 混入は5パターン:誤push/誤merge/rebase・cherry-pick混入/ファイル単位/広範
  • まずgit log --cherry-mark --left-rightで影響範囲を正確に特定
  • safety branchを切ってから履歴操作を開始(reflog併用)
  • 公開履歴はrevertで打ち消し(revert -m 1はマージ用)
  • ファイル単位ならgit restore --source <X^> -- file
  • どうしても履歴書き換えが必要なら合意+--force-with-lease
  • 予防:branch protection/PRベース/pull.rebase=true/commit前ブランチ確認

pushした後に別ブランチの変更が混入するのは、初級者〜中堅でも起きる事故です。対処の肝は「公開履歴を壊さないで戻す」こと。revertgit restore --sourceを使い分ければ、多くの場合は履歴を残しながら修正可能です。どうしても整理が必要な場合はsafety branch+--force-with-lease+チーム通知をセットで。予防はbranch protection/PR運用/pull.rebase設定で確実に強化できます。