feature ブランチで typo 混じりの commit を push してしまった。
ローカルだけなら --amend で済むが、リモートに上がった瞬間それでは足りなくなる。
前提:今どういう状態か
local: A --- B --- C(mistake) ← HEAD
remote: A --- B --- C(mistake) ← origin/feature/xyz
やりたいのは「C をなかったことにして、正しい内容で push し直す」こと。
git reset --soft HEAD^ で commit だけ巻き戻す
git reset --soft HEAD^
--soft は HEAD の位置だけを1つ前に戻す。
ステージングエリアとワーキングツリーには一切触れない。
つまり C の変更内容はステージ済みのまま残る。
local: A --- B ← HEAD(ステージには C の差分が残っている)
remote: A --- B --- C(mistake)
reset のオプション比較
| オプション | HEAD | ステージ | ワーキングツリー |
|---|---|---|---|
--soft |
戻す | そのまま | そのまま |
--mixed(デフォルト) |
戻す | 戻す | そのまま |
--hard |
戻す | 戻す | 戻す |
--hardは変更が完全に消える。取り消したいだけで変更内容は残したい場合は--soft一択。
ステージに残っている差分を修正して commit し直す
# 必要な修正を加える
git add -A
git commit -m "fix: 正しいメッセージ"
git push --force-with-lease でリモートを上書きする
ローカルの履歴を書き換えたことでリモートと分岐し、fast-forward できない状態になったので、通常の git push は rejected される。
$ git push origin feature/xyz
! [rejected] feature/xyz -> feature/xyz (non-fast-forward)
ここで --force を使えば通るが、他人の push を踏み潰すリスクがある。代わりに --force-with-lease を使う。
git push --force-with-lease origin feature/xyz
--force vs --force-with-lease
| 項目 | --force |
--force-with-lease |
|---|---|---|
| 他人の commit を踏み潰す | する | しない(reject される) |
| 検証ロジック | なし | ローカルの remote-tracking ref とリモート HEAD を比較 |
| チーム開発での安全性 | 低い | 高い |
git fetch した直後に --force-with-lease すると、ローカルの ref が最新に更新されているため安全弁が効かなくなる。fetch と force push をワンライナーで繋げないこと。
一連の流れ
# 1. 直前の commit を取り消す(変更はステージに残る)
git reset --soft HEAD^
# 2. 必要なら修正
git diff --cached # ステージの中身を確認
vim src/main.ts # 修正
git add -A
# 3. commit し直す
git commit -m "feat: 正しい内容で再 commit"
# 4. リモートに安全に force push
git push --force-with-lease origin feature/xyz
複数 commit を戻したいとき
HEAD^ の部分を変えるだけで対応できる。
# 直近 3 commit を取り消してステージに残す
git reset --soft HEAD~3
HEAD~Nは N 個前の commit を指す。HEAD^はHEAD~1と同義。
すでに他のメンバーがその commit を元にブランチを切っている場合は、force push ではなく git revert で打ち消し commit を積むほうが安全。履歴を書き換えていいのは「自分しか触っていないブランチ」が原則。
reflog — やらかしたときの保険
reset 自体を間違えても reflog から復元できる。
git reflog
abc1234 HEAD@{0}: reset: moving to HEAD^
def5678 HEAD@{1}: commit: 間違えた commit
# reset 前の状態に戻す
git reset --soft def5678
reflog はローカルに残り続けるので、--hard で消した変更すら救出できる。ただしデフォルトで 90日 を過ぎると GC で消える。