Gitでローカルおよびリモートのコミットを安全に取り消す方法

reset、revert、reflog、force-with-leaseを使って、作業を失ったり共有ブランチを壊したりせずにGitコミットを安全に取り消す方法。

Gitでローカルおよびリモートのコミットを安全に取り消す方法

Gitコミットの取り消しは簡単です。しかし、作業を失ったり、ブランチ上の他の人を驚かせたりせずに、正しいことを取り消すには判断力が必要です。

最初の質問は「どのコマンドを実行するか?」ではありません。「誰がこのコミットを見たか?」です。コミットが自分のラップトップにしか存在しない場合、通常はgit resetgit commit --amendで書き換えられます。コミットがすでに他の人が使うブランチにプッシュされている場合は、git revertを優先してください。これにより履歴がそのまま残り、悪い変更を元に戻す新しいコミットが作成されます。

履歴を変更する前に、現在の状態のスナップショットを取得しておきましょう:

git status
git branch backup-before-undo
git log --oneline --decorate -5

この一時的なブランチは安価な保険です。リセットしすぎたり、考えが変わったりした場合でも、古いコミットには名前が残っています。

コミットがプッシュされていない場合

プッシュしていないローカルコミットには、通常git resetが最もクリーンなツールです。ブランチポインタを後方に移動します。選択するモードによって、ファイルの扱いが決まります。

コミットメッセージが間違っていたり、小さなファイルを1つ忘れた場合は--softを使用します:

git reset --soft HEAD~1

最後のコミットは消えますが、変更はステージングされたままです。不足しているファイルを追加して、再度コミットできます:

git add missing-file.yml
git commit -m "デプロイ設定を更新"

変更を作業ツリーに戻し、ステージングを解除したい場合は、デフォルトのmixedリセットを使用します:

git reset HEAD~1

これは「このコミットを取り消すが、編集は保持する」という日常的なコマンドです。1つのコミットを2つの小さなコミットに分割したい場合や、実際のコードと一緒にデバッグ用のprint文をコミットしてしまった場合に便利です。

ローカルの変更を本当に破棄したい場合にのみ--hardを使用します:

git reset --hard HEAD~1

これにより、追跡対象ファイルが以前のコミットにリセットされます。確認なしで実行されます。追跡対象ファイルに未コミットの作業がある場合、作業ツリーから消える可能性があります。まずgit statusを確認し、戻したいものがあればstashまたはブランチを作成してください。

最新のローカルコミットを小さく修正するには、git commit --amendがリセットより良い場合が多いです:

git add corrected-file.js
git commit --amend

これにより、最後のコミットが新しいものに置き換わります。同じルールが適用されます:プッシュ前は自由に修正、プッシュ後は注意。

コミットがすでにプッシュされている場合

共有ブランチでは、履歴を書き換える強い理由がない限りgit revertを使用します。Revertは逆のパッチを適用する新しいコミットを作成します。

git revert a1b2c3d

このコマンドはエディタを開き、生成されたメッセージを表示します。保存すると、Gitが新しいコミットを作成します。元のコミットは履歴に残ります。これはmainmasterdevelop、リリースブランチ、およびチームメイトがプルしている可能性のあるブランチでまさに必要な動作です。

連続する複数のコミットを元に戻す必要がある場合、1つのステージングされたバッチで実行できます:

git revert --no-commit HEAD~3..HEAD
git status
git commit -m "最近のデプロイ変更を元に戻す"

範囲を注意深く読んでください。HEAD~3..HEADは最後の3つのコミットを意味し、HEAD~3自体は含みません。不明な場合は、まずコミットをリスト表示します:

git log --oneline HEAD~3..HEAD

マージコミットには追加の判断が必要です。マージには複数の親があるため、Gitはどの側をメインラインとして扱うかを知る必要があります:

git revert -m 1 <merge-commit-sha>

ほとんどのチームは、ターゲットブランチからのフィーチャーブランチマージを元に戻す場合に-m 1を使用しますが、盲目的に実行しないでください。git show --summary <sha>でマージコミットを確認し、親の順序を確認してください。

間違ったものをプッシュしてしまい、削除する必要がある場合

時にはrevertだけでは不十分です。秘密情報、巨大なバイナリ、または顧客の個人データをプッシュした場合、revertは最新のツリーから削除するだけです。機密コンテンツは履歴に残ります。これは単なるGitクリーンアップの問題ではなく、インシデント対応の問題になります。

秘密情報の場合は、まず資格情報をローテーションします。次に、git filter-repo、BFG Repo-Cleaner、またはホスト固有の秘密情報削除プロセスなどの適切な履歴書き換えツールを使用して、データを履歴から削除します。リポジトリ所有者と調整し、悪いコミットを削除する前に誰でもアクセスして取得できた可能性があることを想定してください。

自分のフィーチャーブランチでの通常の誤ったコミットの場合、リモートブランチの書き換えは許容される場合があります。ローカルでリセットし、リース付きで強制プッシュします:

git reset --hard HEAD~1
git push --force-with-lease origin my-feature-branch

--force-with-leaseはより安全な形式です。最後のフェッチ以降に誰かが新しい作業をプッシュした場合、リモートの更新を拒否します。それでも強制プッシュです。ブランチを書き換えます。個人用または調整済みのフィーチャーブランチでのみ使用し、共有ブランチでは軽率に使用しないでください。

強制プッシュ前の良い習慣は次のとおりです:

git fetch origin
git log --oneline --left-right --graph origin/my-feature-branch...my-feature-branch

これにより、リモートにのみ存在するものとローカルにのみ存在するものが表示されます。左側に他の人のコミットが含まれている場合は、止めてその人と話し合ってください。

reflogでの復旧

git reflogは、多くの悪い午後を救うコマンドです。ローカルのHEADとブランチ参照が最近どこを指していたかを記録します。間違った場所にリセットしたり、ブランチを削除したり、間違ったコミットを修正したりした場合、reflogは古いコミットのSHAを知っていることがよくあります。

git reflog

次のようなものが表示されるかもしれません:

7cc8a91 HEAD@{0}: reset: moving to HEAD~1
2b41f0d HEAD@{1}: commit: add retry around deploy step

古いコミットを復元するには、そのコミットでブランチを作成します:

git branch recovered-deploy-work 2b41f0d

すぐに別のハードリセットを実行する代わりに、ブランチを作成することをお勧めします。安定したハンドルが得られ、復元された作業を落ち着いて検査できます:

git show recovered-deploy-work
git switch recovered-deploy-work

Reflogはローカルです。チームメイトのreflogにはあなたの失われたローカルコミットは含まれず、リモートホストが同じ復旧パスを公開するとは限りません。また、Gitのガベージコレクション設定に従って時間とともに削除されるため、アーカイブストレージではなく、最近のセーフティネットとして扱ってください。

実用的な判断ガイド

早すぎるコミットをしてプッシュしていない場合、git reset --soft HEAD~1またはgit reset HEAD~1を使用します。

間違ったファイルをコミットし、編集をローカルで削除したい場合、git statusを確認した後にのみgit reset --hard HEAD~1を使用します。

悪いコミットがすでに共有ブランチにある場合、git revert <sha>を使用します。

フィーチャーブランチがリモートにあるが、自分だけが使用している場合、git resetgit push --force-with-leaseは通常許容されます。

コミットに秘密情報や機密データが含まれている場合、revertに頼らないでください。秘密情報をローテーションし、適切なツールで履歴を書き換え、クリーンアップを調整してください。

最も安全なGit取り消しワークフローは退屈です:まず検査し、バックアップブランチを作成し、コミットが共有されているかどうかに基づいてコマンドを選択し、自分の復旧試行から復旧する必要がある場合にreflogを使用します。