マガジン

AIが入れた変更がマージ済みのとき:git revertとマージコミットの戻し方

AIを使いこなす

Claudeで執筆し、GPTで自動校閲・修正しています。

積み木の列を崩さず、末尾に別の積み木を追加する手元。履歴を残して変更を打ち消す操作を表しています。
記事の内容をもとに制作したAI生成イメージです。実際の製品画面や検証結果の写真ではありません。

結論から言うと、共有ブランチにマージ済みの変更は、履歴を巻き戻さず git revert で「打ち消すコミット」を新しく積むのが基本です。通常のコミットなら git revert <ハッシュ> だけで済みます。マージコミットは、どちらの親を基準に戻すかを -m で指定する必要があり、多くの場合は git revert -m 1 <マージコミットのハッシュ> です。 間違えて戻したときも、履歴は消えていないので慌てなくて大丈夫です。打ち消しコミットをさらに revert すれば元に戻せます。この記事では、この3つ(通常コミット、マージコミット、取り消しの取り消し)を順に示します。コマンドは Git 公式ドキュメントの記述に沿っていますが、記事中のハッシュやブランチ名は説明用の例で、実際のリポジトリで実行した結果ではありません。

この記事の目次
  1. まず「通常のコミット」か「マージコミット」かを見分ける
  2. 通常のコミットを戻す手順
  3. マージコミットを戻す手順(-m 1 の意味)
  4. GitHub の画面から戻す方法
  5. 取り消しを取り消す手順
  6. AIに任せる範囲と、人が確認すること

まず「通常のコミット」か「マージコミット」かを見分ける

手順が分かれるので、最初に戻したいコミットの種類を確認します。

bash
git log --oneline --graph -n 15
git show -s --format="%h 親=%p %s" <戻したいコミット>

「親」に表示されるハッシュが1つなら通常のコミット、2つならマージコミットです。

  • 直接コミットされた、または別の方法で1本のコミットとして入った、親の数:1、使うコマンド:git revert <hash>
  • 「Merge pull request …」などのマージコミット、親の数:2、使うコマンド:git revert -m 1 <hash>
  • 複数の通常コミットが並んでいる、親の数:各1、使うコマンド:新しい順に revert(後述)

GitHub 上のプルリクエストの取り込み方法によっては、マージコミットが作られず通常のコミットになることがあります。見た目の名前ではなく、必ず親の数で判断してください。

通常のコミットを戻す手順

共有ブランチ(ここでは main)を直接いじらず、戻し用のブランチを切るのが安全です。

bash
git fetch origin
git switch -c revert-ai-change origin/main
git revert <戻したいコミット>
git push -u origin revert-ai-change

エディタでメッセージが開いたら、何を戻す理由かを1行足しておくと後で追いやすくなります。エディタを開きたくないときは --no-edit を付けます。

AIが数コミットに分けて入れた変更をまとめて戻すなら、-n(--no-commit)を使います。作業ツリーに打ち消しを適用するだけでコミットはしないので、複数を1つのコミットにまとめられます。

bash
git revert -n <新しいコミット> <古いコミット>
git commit -m "AIが追加した○○機能の変更を取り消す"

後から入ったコミットが前のコミットに依存していることが多いため、新しいほうから順に並べるのが無難です(この順序は編集上の提案で、公式の規則ではありません)。

途中で競合したら、該当ファイルを直してから git revert --continue、やめるなら git revert --abort です。

マージコミットを戻す手順(-m 1 の意味)

マージコミットは親が2つあるため、何も指定しないと Git はどちら側の変更を消すべきか判断できません。git revert の公式ドキュメントでは、-m に親の番号(1始まり)を渡し、その親を基準にして戻すと説明されています。

bash
git switch -c revert-ai-merge origin/main
git revert -m 1 <マージコミット>

通常、main に取り込む側で git merge した場合、親1は取り込み先の main 側、親2は取り込まれた作業ブランチ側です。つまり -m 1 は「main 側を残し、作業ブランチ側の変更を消す」指定になります。念のため、次で親の並びを確認できます。

bash
git show -s --format="%P" <マージコミット>

出力の1番目が main 側のコミットなら -m 1 で合っています。実行後は、本当に狙った変更だけが消えたかを差分で確認してください。

bash
git show --stat HEAD

注意:マージを戻すと「再マージしても戻ってこない」

git revert の公式ドキュメントは、マージを revert すると、そのマージで入ったツリーの変更は以後不要だと宣言したことになる、と説明しています。実際には、同じブランチをあとでもう一度マージしても、すでに取り込み済みの履歴として扱われ、以前の変更が戻ってきません。

AIに直させた続きの修正を同じブランチで進めたくなりますが、その場合は次の「取り消しの取り消し」を先に行うか、戻した後の main から新しいブランチを切り、必要な変更全体を新しいコミットとして作り直すほうが混乱しません(後者は編集上の提案です)。ブランチを切るだけでは、以前の変更は戻りません。

GitHub の画面から戻す方法

マージがGitHub上のプルリクエスト経由なら、画面からも戻せます。GitHub Docsによると、書き込み権限があれば、対象のプルリクエストの下部にある「Revert」ボタンから、元のマージコミットを戻す新しいプルリクエストが作られます。それをマージすれば完了です。

ただし、戻す際に競合が起きる場合や、元のプルリクエストがGitHub上でマージされていない場合は、手元で Git を使って戻す必要があると記載されています。本記事の手順はその場合にも使えます。

取り消しを取り消す手順

戻したあとに「やっぱり必要だった」と分かることはよくあります。履歴を書き換える必要はなく、打ち消しコミットを revert します。

bash
git log --oneline -n 10
git switch -c restore-ai-change origin/main
git revert <打ち消しコミット>

ここで指定するのは、元の変更ではなく、先ほど作った「Revert …」のコミットです。マージコミットを戻した場合も同様で、この2回目の revert には -m は要りません(打ち消しコミットは通常のコミットで、親が1つのため)。これは、git revert の公式ドキュメントにある「既存コミットの変更を新しいコミットで打ち消す」仕組みを、打ち消しコミットにも適用する方法です。

戻したあとに、AIが直した新しい版を入れたい場合は、復活させたうえで追加の修正コミットを重ねるか、戻した後の main から切った別ブランチに必要な変更全体を新しいコミットとして作り直してレビューに出すほうが、差分が追いやすくなります。

AIに任せる範囲と、人が確認すること

コマンドの案出しはAIに任せられますが、実行前に人が履歴を見るべきです。次の依頼文は、そのままコピーして使える例です。機密を含むログは、ハッシュと件名だけに絞って貼ってください。

text
共有ブランチ main にマージ済みの変更を、履歴を書き換えずに取り消したいです。
次の git log --graph の出力と、各コミットの親のハッシュを渡します。
1. 取り消すべきコミットが通常かマージかを判定してください。
2. 戻し用ブランチを切るところから、git revert のコマンドを順に出してください。
   マージコミットなら -m に渡す番号と、その根拠を説明してください。
3. force push や reset --hard は使わないでください。
4. 実行後に確認すべきコマンドも出してください。

[git log --oneline --graph の出力]
[git show -s --format="%h %P %s" の出力]

人が確認する点は次のとおりです。

  • 提案された -m の番号が、%P の並びと一致しているか
  • force push や reset --hard が紛れていないか(共有履歴を書き換えると、他の人の手元とずれます)
  • 戻したあとに git show --stat HEAD で想定外のファイルが消えていないか
  • テストを実行し、戻した状態でも動くか

AIの提案が「何番を指定するか」を根拠なく断定していたら、そのまま実行せず、%P の出力を見せて再確認させると間違いを防げます。

出典・参考資料