【Git】mergeコミットを取り消して履歴を元に戻す方法

【Git】mergeコミットを取り消して履歴を元に戻す方法 Git

意図しないマージを取り消して履歴を元に戻したいとき、公開済みのブランチでは履歴を書き換えずに「マージを打ち消す」ことが基本方針になります。
本記事では、公開ブランチで安全に使えるgit revert -mを中心に、未pushならgit resetで巻き戻す方法、取り消し後に必要な変更だけを取り戻す手順、コンフリクト時の進め方までを整理します。

まず状況確認:どのマージを、どの親を基準に取り消すか

最初に対象のマージコミット(例:M)を特定し、親の関係を把握します。一般的な2親のマージでは「親1=マージ先(今いるブランチ側)」「親2=取り込んだ側」になっていることが多いですが、確実に確認しておくと安全です。

# 参照を更新して履歴を俯瞰
git fetch --prune
git log --oneline --decorate --graph -n 30

# 対象マージ M の親と差分を確認
git show --no-patch --pretty=raw <M>
git show -m --name-status <M>

親関係が分かったら、公開ブランチではrevert、未pushであればresetの選択肢が取れます。

公開ブランチで安全に取り消す:git revert -m

git revertは指定コミットの変更を打ち消す新しいコミットを作り、履歴は維持されます。マージを取り消すときは基準にする親を-mで指定します。通常は親1(マージ先)を基準にします。

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

# コンフリクトが出たら解消→ステージ→続行
git add .
git commit

# リモートへ反映
git push

取り消し後、必要な変更だけを別ブランチへcherry-pickして再適用すれば、履歴の整合性を保ちながら最小限の修正にできます。

未pushなら履歴を巻き戻せる:git resetでマージ自体を無かったことに

まだ誰にも共有していないマージなら、マージ直前のコミットまでresetで戻すのが最短です。作業ツリーも巻き戻すなら--hard、変更は残したいなら--mixedを使います。

# マージ直前のコミットを base として巻き戻す
git reset --hard <base>     # 作業ツリーごと戻す
# もしくは
git reset --mixed <base>    # 変更は残して履歴だけ戻す

# その後、必要なら整えたうえでpush(公開後は reset は不可)

すでにpushしているブランチでresetを使う場合は、強制pushが必要になり共同作業に影響します。やむを得ない場合のみ合意のうえ--force-with-leaseを使います。

git push --force-with-lease

取り消し後に「必要な変更だけ」を取り戻す方法

マージ全体は誤りでも、中に正しい修正が含まれていることはよくあります。取り消しコミットの後で、必要なコミットだけを元ブランチから選んでcherry-pickすれば、意図した差分だけを復活できます。

# 例:取り込み元ブランチ side のコミット C1, C2 だけを再適用
git cherry-pick <C1> <C2>

ファイル単位で部分的に戻したい場合は、対象コミットの直前から内容を引き戻します。

# 例:マージ M の影響前(M^)の内容に特定ファイルを戻す
git restore --source <M^> -- path/to/fileA path/to/fileB
git add .
git commit -m "Restore files to state before <M>"

コンフリクトが発生したときの進め方

revert -mでも差分が大きいとコンフリクトになります。衝突箇所を解消してステージし、通常のマージと同様にコミットで完了します。手に負えない場合は一旦取り消して方針を変えます。

# 進行中の状態を確認
git status

# 解消→続行
git add .
git commit     # revert の最終コミット

# やり直す場合(revert を取消すにはこのコミットを git reset で落とす or 逆revert)
# 逆revert(取り消しの取り消し)で元に戻す例
git revert <revert-commit-hash>

複数のマージを連続で取り消す場合の注意

時系列に沿って一つずつrevert -mを行い、その都度テストを通すのが安全です。対話的にまとめたいときは、作業用ブランチで順番に取り消してから一つのPRにまとめます。三つ以上の親を持つオクトパスマージでも-mで基準親を選べますが、衝突が増えやすいため粒度を小さく進めます。

やる前の保険と、巻き戻し経路の確保

誤操作に備えて退避ブランチを切り、reflogで戻れることを確認してから着手します。これだけで心理的安全性が上がります。

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

運用面の再発防止

デフォルトブランチには保護設定を入れ、PR経由でのみ統合する運用にすると誤マージが減ります。取り込み前にgit fetchで最新を取り、fast-forward可能なときは--ff-onlyで履歴を汚さない方針を徹底します。日常の同期はリベースを基本にして差分を小さく保つのも有効です。

# 推奨設定例
git config --global pull.ff only
git config --global pull.rebase true
git config --global fetch.prune true

まとめ

公開済みの誤マージはgit revert -mで「機能的に取り消す」のが安全で、未pushならgit resetでマージ自体をなかったことにできます。
取り消し後は必要な変更だけをcherry-pickrestoreで丁寧に取り戻し、コンフリクトは通常のマージ同様に解消します。
退避ブランチとreflogで退路を確保しつつ、保護設定と取り込みルールを整えることで、同種の事故は確実に減らせます。