ひとことで言うと
# pushする前 → ファイルは残してコミットだけ消す
git reset --soft HEAD~1
# pushした後 → 取り消しコミットを追加(履歴は残る)
git revert HEAD --no-edit && git push
# .envを誤pushした緊急対応
git rm --cached .env && echo ".env" >> .gitignore
git commit -m "remove .env from tracking"
# ← キーの無効化を先にやること。gitの操作より優先
--soft はファイルを残してコミットだけ消す。--hard はファイルごと消える。間違えると取り返しがつかない。
やりたかったこと
.envファイルを誤ってコミットしていたことに、pushした後で気づいた。GitHubのリポジトリをブラウザで確認したら.envの中身がそのまま公開されていた。STRIPE_SECRET_KEY=sk_live_xxxxxxxxという行がコミットの差分ビューに丸見えになっていて、かなり焦った。
しかも気づくまでに3日かかった。GitHubのセキュリティbotから「Secret exposed in commit」という通知のissueが自動で作られていて、それで初めて気づいた。3日間、誰でも.envの中身を見られる状態が続いていた。Stripeのシークレットキーが悪用されていないかを確認するためにStripeダッシュボードのアクセスログを確認して回る羽目になった。
急いで取り消そうとしたが、「コミットを取り消す」方法を調べるとgit resetとgit revertの2種類が出てきてどちらを使えばいいか判断できなかった。コマンドを間違えて--hardを指定してしまい、.envとは関係ない1時間分のコードがごっそり消えてしまうという失敗もした。
git reset --hard HEAD~1
# 実行後に git status したら全部消えていた
最終的にgit log --onelineとgit reflogで現在地を確認してから操作をやり直したら短時間で解決した。
環境
- Git 2.44.0
- Windows 11 / Ubuntu 22.04
- GitHub(リモートリポジトリあり)
試したこと・うまくいかなかったこと
git revert HEADを試した → GitHubで古いコミットを開いたら.envが見えた
「revertの方が安全」という情報を見てgit revert HEADを試した。「Revert “add .env file”」という新しいコミットが作られた。「これで取り消せた」と思ってブラウザでGitHubを開いたら、古いコミットをクリックすると.envの内容が丸見えのままだった。git revertは「取り消しコミット」を追加するだけで、過去のコミット自体は履歴に残り続けることを知らなかった。
git reset --hard HEAD~1を使った → 1時間分のコードが消えた
「commit自体を完全に消したい」と思ってgit reset --hard HEAD~1を使った。コミットは確かに消えたが、その時に書いていた.envとは無関係のコードも全部消えた。--softと--hardの違いを理解せずに使った結果で、git statusを見ても何も残っていなかった。
git reset --hard HEAD~1
# 実行後
git status
# nothing to commit, working tree clean
# 1時間分の変更が跡形もなく消えた
push後にgit commit --amendを試した → force pushが必要になった
コミットメッセージを修正しようとしてgit commit --amendを使ったら、すでにpushした後だったのでリモートとの差分が生まれてしまいgit push --forceが必要になった。チームリポジトリだったのでforce pushは使えなかった。
結局git revert HEAD --no-editで「コミットメッセージを誤って記入した」を取り消すコミットを追加することになった。amendは便利だが「まだpushしていないか」の確認を先に行う癖をつけておけばこの失敗は防げた。
git filter-repoを使って全履歴から削除しようとしたら、Python環境でハマった
pip install git-filter-repoを試したらpipコマンドが見つからないエラーになった。Windows環境でPythonをインストールしてあったが、インストール時に「Add Python to PATH」のチェックを外していたのが原因だった。Pythonのインストーラーを開き直して「Repair」から「Add Python to PATH」を有効にして再インストールした。その後pip install git-filter-repoが通って.envの全履歴削除ができた。git filter-repoはPython 3.6以上が必要で、バージョンが古い場合はpython --versionで確認してから進めるといい。
解決策
状況に合わせてコマンドを使い分ける。まだpushしていないか、すでにpushしたかで方法が変わる。
1. 直前のcommitを取り消したい(ファイルの変更は残す)
pushする前の場合はこれが最も安全。 コミットは消えるが、変更したファイルはstaged状態で残る。
git reset --soft HEAD~1
実行後はgit statusで確認するとファイルが「Changes to be committed」の状態になっている。.envをgit restore --staged .envでアンステージしてから、.gitignoreに追加して改めてコミットする。
git restore --staged .env
echo ".env" >> .gitignore
git add .gitignore
git commit -m "fix: remove .env from tracking"
.gitignoreに先に追加しておかないと、次のコミット時にまた.envが含まれてしまう。「アンステージ」→「.gitignoreに追加」→「.gitignoreをコミット」という順番を必ず守る。
2. 直前のcommitを完全に取り消したい(ファイルの変更も消す)
ファイルの変更ごと全部なかったことにしたい場合。元に戻せないので実行前にgit diff HEAD~1でどんな変更が消えるか必ず確認する。
git diff HEAD~1 # 消える変更を先に確認
git reset --hard HEAD~1
--hardを実行する前に必ず現在のコミットハッシュをメモしておく。万が一間違えた場合でもgit reflogから復元できる(後述)。
3. commitメッセージだけ変更したい
まだpushしていない場合のみ使う。pushした後に使うとforce pushが必要になる。
git commit --amend -m "新しいメッセージ"
amendを使う前に「まだpushしていないか」を確認するにはgit statusの出力を見る。「Your branch is ahead of ‘origin/main’ by 1 commit」が出ていればpush前でamendを使っていい。「Your branch is up to date」が出ていれば既にpushされているのでamendは使わない。
4. pushした後に取り消したい場合
pushした後はresetで履歴を書き換えるとチームに迷惑がかかる。revertで「取り消しコミット」を新しく作る方法が安全。
git revert HEAD --no-edit
git push
ただし.envを含むコミットをpushしてしまった場合は、git revertだけでは不十分。GitHubのコミット履歴には.envの内容が残っているので、パスワードや秘密鍵はすぐに変更・再発行するのが先決。その後、履歴から完全に削除するにはgit filter-repoを使う。
# git-filter-repoをインストール(Python環境が必要)
pip install git-filter-repo
# 特定ファイルを全履歴から削除
git filter-repo --path .env --invert-paths
実行後はgit push --forceが必要になる。チームリポジトリの場合は全メンバーへの事前告知が必要(force push後に全員がcloneし直す必要がある)。
5. —hardで消してしまった変更を復元する
git reset --hardで消してしまっても、git reflogで一定期間は復元できる。
git reflog
実行すると操作の履歴が出てくる。
a1b2c3d HEAD@{0}: reset: moving to HEAD~1
d4e5f6g HEAD@{1}: commit: 1時間かけて書いた変更
HEAD@{1}のハッシュに戻したい場合:
git reset --hard d4e5f6g
git reflogのエントリはデフォルトで90日間保持される。git reset --hardを実行する前に現在のハッシュをメモしておく癖をつけておくと、より確実に復元できる。
6. .envを誤ってpushした時の緊急対応手順
Step 1:認証情報を即時無効化する(最優先)
- Stripe/AWS/GCPなどのダッシュボードで該当キーを削除または無効化
- GitHubのPATが含まれている場合はDeveloper settingsから削除
- DBパスワードが含まれている場合はDBのパスワードを即時変更
- 新しいキー/パスワードを発行して.envを更新する
Step 2:.gitignoreに追加してローカルでアンステージ
echo ".env" >> .gitignore
git rm --cached .env
git add .gitignore
git commit -m "remove .env from tracking"
Step 3:git filter-repoで全履歴から削除
pip install git-filter-repo
git filter-repo --path .env --invert-paths
Step 4:force pushしてチームに通知
git push --force origin main
force push前に必ずチームメンバーに「force pushします。完了したら全員git cloneし直してください」と連絡する。
Step 5:GitHubのキャッシュクリアを依頼(必要に応じて)
force push後もGitHubのキャッシュに古いコミットが残ることがある。該当コミットのURLにアクセスして.envの内容が見えないことを確認する。まだ見える場合はGitHub Supportに連絡する。
ハマったポイント
git revertは「安全な取り消し方法」だと思っていたが、実際には「取り消しコミットを追加する方法」で過去のコミット内容は履歴に残り続ける。.envを含むコミットをgit revertしてもGitHubの古いコミットをブラウザで開けば.envの内容は見えてしまう。pushしてしまった機密情報は「リポジトリから削除した」ではなく「漏洩済み」として扱い、コミット取り消しと認証情報の再発行は別々の問題として対処する必要があった--softと--hardの違いを理解せずにgit reset --hard HEAD~1を使ったら、.envとは関係ない1時間分のコードが跡形もなく消えた。--softはコミットだけ消えてファイルはstaged状態で残る、--hardはコミット・ステージ・ワーキングツリーすべて消える。「コードを消さずにコミットだけ取り消したい」なら--soft一択だった- push後に
git commit --amendを使ったらforce pushが必要になった。push後のコミットに--amendを使うと「ローカルのコミット履歴がリモートより進んでいる」状態になり、次のgit pushが弾かれる。git statusで「Your branch is ahead of origin」が出ていれば push 前、「up to date」なら push 済みという確認を先に行う git reset --hardで消したコードは「永遠に戻らない」と思っていたが、90日以内ならgit reflogからコミットのハッシュを探して復元できる。これを知らなくて1時間分の作業をゼロから書き直した。git reset --hardを実行する前に現在のハッシュをメモしておくか、git reflogの使い方を覚えておけば取り返しがつく.envをpushしてからgit操作だけに集中して認証情報の無効化を後回しにしてしまった。git操作で履歴を消している間も漏洩は続いている。正しい優先順位は「まず認証情報を無効化、その後履歴から削除」で、この2つは並行して進めるべき別々の作業だった
よくある質問
Q: git reset --soft と git reset --hard の違いは?
--soft はコミットのみ取り消してファイルの変更はstagingエリアに残す。--hard はコミット・staging・作業ディレクトリすべて取り消してファイルの変更ごと消える。「コミットだけ取り消したい、変更は残したい」なら--soft一択。--hardは実行前に必ずgit diff HEAD~1で消える変更を確認する。
Q: git revertは何のために使いますか?
pushした後に「コミットを取り消した」という事実を履歴に残したい場合に使う。revertは「取り消しコミット」を新しく追加するだけで過去のコミット自体は履歴に残る。チームリポジトリでforce pushを使えない状況に適している。.envなど機密情報が含まれるコミットの場合、revertだけでは過去のコミットに内容が残るので注意。
Q: git reset --hard で消したファイルを戻せますか?
git reflog を使えば90日以内なら戻せる。git reflog を実行してリセット前のコミットハッシュを探し、git reset --hard <ハッシュ> で戻す。reflog には git reset --hard を実行する直前の状態が HEAD@{1} として残っている。
Q: .envをpushしてしまった。gitの操作より先にやることは? 認証情報の無効化が最優先。Stripe・AWS・GCPなど該当するダッシュボードでキーを即時削除して新しいキーを発行する。git操作で履歴を消している間も漏洩は続いているので、「まず無効化、それからgit操作」という順番を守る。
関連記事
- GitHubで初めてリポジトリを作ってpushする手順
- WindowsにGitをインストールして初期設定する方法
- GitのブランチをCLIで作成・切り替える基本コマンド
- Gitで.gitignoreを設定してファイルを管理対象から外す方法