Git で取り消すには?reset・revert・restore の違いと使い分け(push の前と後)

PR
Git で取り消すには?reset・revert・restore の違いと使い分け(push の前と後)
この記事は約36分で読めます。

Git で取り消すときは、取り消したいものが push の前か後かで、使うコマンドが変わります。push の前(自分の手元にしかない)なら、変更を捨てる git restore、直前のコミットを直す git commit --amend、コミットを外す git reset が使えます。push の後(GitHub などのリモートにもある)に、共有しているブランチで取り消すときは、ふつうは git revert で、打ち消す新しいコミットを足します。

reset・revert・restore は名前が似ていますが、動かすものが違います。この記事は、初心者の方に向けて、公式の文書(git-scm.com)に沿って、取り消し方を push の前と後に分けてまとめたものです。コマンドの出力は git 2.39.3(Apple Git-145・macOS)のものです(2026年10月時点の最新は 2.56.0 です)。Git の表示は、手元の git 2.39.3 では日本語の環境でも英語で出ました。ハッシュ(コミットの名前になる英数字)と日時は、流すたびに変わります。

この記事でわかること
  • reset・revert・restore の違い(動かすものが違う)と、push の前と後の早見表
  • push の前にできること(変更を捨てる、add を取り消す、直前のコミットを直す、コミットを外す)
  • push の後は、revert で「打ち消すコミット」を足すこと
  • push 済みのコミットを reset や amend すると push が断られる理由と、強制 push の注意
  • 消してしまったものを reflog で探す方法と、戻せないもの・期限

取り消したいのはどこ?reset・revert・restore の違いと早見

名前の似たコマンドが3つあります。公式の git の文書には、この3つの違いを説明した節(Reset, restore and revert)があります。

コマンド公式の説明の要点
git restore作業ツリーのファイルを、インデックス(git add した状態)か別のコミットから戻す。ブランチは動かさない
git resetブランチを更新する。先端を動かして、コミットを足したり外したりする。コミットの履歴が変わる
git revertほかのコミットが入れた変更を打ち消す、新しいコミットを作る

つまり、restore はファイル、reset はブランチの先端、revert は「打ち消すコミットを足す」ものです。なお、reset はインデックスを戻すのにも使えて、そこは restore と重なります。restore も、インデックスのファイルを別のコミットから戻せます。

変更があるのは、作業ツリー(手元で編集しているファイルが置いてある場所)、インデックス(git add した状態。ステージとも呼びます)、コミットの3つのどれかです。どこにあるかは git status が分けて出し、それぞれの戻し方も案内してくれます(git 2.39.3 の出力)。

$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   b.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   a.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	new.txt

b.txt は書き換えて git add した変更(Changes to be committed)、a.txt は書き換えただけで git add していない変更(Changes not staged for commit)です。new.txt は、Git がまだ追跡していない(まだ git add していない)ファイルです。git status の見方は、Git コマンドのよく使う使い方の記事でも紹介しています。

取り消したいものごとに、push の前と後で使うコマンドを並べると、次のようになります(公式の文書の説明を、この記事で並べ直した表です)。

取り消したいものpush の前(自分の手元だけ)push の後(リモートにもある)
まだ git add していない変更git restore <ファイル>(捨てた変更は戻らない。Pro Git)push とは関係ない。左と同じ
git add したことgit restore --staged <ファイル> または git reset <ファイル>同上
直前のコミットの中身・メッセージgit commit --amendamend すると push が断られる。強制 push は「誰も取ってきていない」と確かなときだけ。共有しているブランチなら revert
コミットそのものgit reset --soft・--mixed・--hardgit revert <コミット>。reset して push すると断られる
マージgit reset --hard ORIG_HEAD(コミットしていない変更も消える)git revert -m 1 <マージコミット>(あとで同じブランチをマージし直すときは注意)
消してしまったコミットgit reflog で探して、reset や branch で戻すreflog は手元の記録

このあとは、push の前の取り消し(restore・amend・reset)、push の後の取り消し(revert)、push 済みのものを reset・amend したときの注意、消してしまったときの reflog、の順に説明します。

まだコミットしていない変更を戻すには?(git restore)

git restore は、git switch と一緒に 2.23 で入ったコマンドです。もとは git checkout ひとつでやっていた「ブランチの切り替え」と「ファイルを戻す」を分けたものです。手元の git 2.39.3 に当たる版の文書には「試験的(experimental)なコマンドで、動きが変わるかもしれない」という注意書きがありましたが、2.51 で試験的の扱いが外れました。手元の git 2.39.3 の git status も、戻し方に git restore を案内しています。

add していない変更を捨てるには?

git restore <ファイル> は、指定したファイルを作業ツリーに戻します。何も付けないと、中身は git add した状態(インデックス)から取ります。a.txt の2行目を書き換えたあとで流すと、元の3行に戻りました(git 2.39.3 の出力)。

$ git restore a.txt
$ cat a.txt
line1
line2
line3

注意: Pro Git(英語)によると、git restore <ファイル> で捨てた変更は、戻りません。公式のリファレンス(git-restore の文書)には書かれていませんが、Pro Git は、このコマンドを「dangerous command(危ないコマンド)」と書いています。そのファイルの手元の変更は消え、最後に git add した版かコミットした版に置き換わるので、その変更が要らないと確かなときだけ使う、という注意です。とっておきたいときは、stash やブランチのほうが一般によい、とも書いています。stash は git stash の使い方の記事で説明しています。

今のフォルダのファイルを全部戻すなら git restore .、リポジトリ全体なら git restore :/ です(公式の文書の例)。間違えて消した(rm した)ファイルも、同じように戻せます(git 2.39.3 の出力)。

$ rm a.txt
$ git status --short
 D a.txt
?? new.txt
$ git restore a.txt
$ cat a.txt
line1
v2
line3

git restore に渡せるのは、Git が知っているファイルです。新しく作って一度も git add していないファイル(未追跡のファイル)を渡すと、エラーになります。ファイルを1つも書かないときもエラーです(git 2.39.3 の出力)。

$ git restore new.txt
error: pathspec 'new.txt' did not match any file(s) known to git
$ git restore
fatal: you must specify path(s) to restore

別のコミットの中身に戻すには?(--source)

--source(-s)でコミットを指すと、そのコミットの中身に戻せます。HEAD~1 は、いまのコミット(HEAD)の1つ前のコミットです(HEAD~3 なら3つ前)。この例では、1つ前のコミットの a.txt は2行目が line2 で、いまは v2 です(git 2.39.3 の出力)。

$ git restore --source=HEAD~1 a.txt
$ cat a.txt
line1
line2
line3
$ git status --short
 M a.txt
?? new.txt

注意: --source で戻すと、作業ツリーにある、そのファイルのコミットしていない変更は上書きされます(git add した中身はインデックスに残ります)。

--staged を付けなければ、変わるのは作業ツリーだけです。git status --short の M a.txt は「git add していない変更」を表すので、git add した状態は変わっていません。

git add を取り消すには?(--staged と git reset)

--staged(-S)を付けると、戻す先がインデックスだけになり、中身は HEAD から取ります。git add を取り消して、作業ツリーの変更は残します。b.txt を書き換えて git add したあとで流しました(git 2.39.3 の出力)。

$ git restore --staged b.txt
$ git status --short
 M b.txt
?? new.txt
$ cat b.txt
b2

git add の取り消しは、ファイルを指定した git reset <ファイル> でもできます。公式の文書では、git reset <ファイル> は git add <ファイル> の逆で、git restore --staged <ファイル> と同じです。変わるのはインデックスだけで、HEAD も作業ツリーも変わりません。表示も違い、git reset は次のように出し、git restore --staged は何も出しません(git 2.39.3 の出力)。

$ git reset b.txt
Unstaged changes after reset:
M	b.txt
$ git status --short
 M b.txt
?? new.txt

Pro Git は、2.23.0 から多くの取り消しで git reset の代わりに git restore を使うようになった、と書いています。また、git reset は --hard を付けると危ないが、ファイルを指定して git add を取り消す使い方は作業ディレクトリのファイルに触らないので、比較的安全、とも書いています。

--staged と --worktree を両方付けると、インデックスと作業ツリーの両方を戻します。次の例では、a.txt を git add したあとにもう一度書き換えたので、MM(git add した変更と、その後の変更の両方がある状態)になっています(git 2.39.3 の出力)。

$ git status --short
MM a.txt
?? new.txt
$ git restore --staged --worktree a.txt
$ git status --short
?? new.txt
$ cat a.txt
line1
v2
line3

直前のコミットを直すには?(git commit --amend)

push する前のコミットにだけ使います。 push したあとで amend するとどうなるかは、下の「push したあとの reset・amend が危ない理由」で説明します。

git commit --amend は、新しいコミットを作って、今のブランチの先端と置き換えます。親と作者(author)は元のコミットと同じで、メッセージを指定しないと、元のコミットのメッセージから始まります。Pro Git は、amend は直すというより、新しいコミットで丸ごと置き換えるもので、前のコミットは無かったかのように履歴に出なくなる、と書いています。そのため、amend のたびにコミットのハッシュが変わります(下の例では d7aa32f → b3b4ae6 → c6b6b4f)。

公式の gitfaq(よくある質問)には、「直前のコミットを間違えた。どう直す?」があります。作業ツリーを直して、git add(ファイルを消すなら git rm)を流し、git commit --amend します。メッセージをそのまま使うなら --no-edit を付けます。メッセージだけ直すなら -m で新しいメッセージを渡します。

次の例は、b.txt だけ git add してコミットしたあとで、入れ忘れた c.txt を足し、メッセージを直したものです(git 2.39.3 の出力)。

$ git commit -m "add b and c"
[main d7aa32f] add b and c
 1 file changed, 1 insertion(+)
 create mode 100644 b.txt
$ git add c.txt
$ git commit --amend --no-edit
[main b3b4ae6] add b and c
 Date: Sat Oct 10 17:55:28 2026 +0900
 2 files changed, 2 insertions(+)
 create mode 100644 b.txt
 create mode 100644 c.txt
$ git commit --amend -m "Add b.txt and c.txt"
[main c6b6b4f] Add b.txt and c.txt
 Date: Sat Oct 10 17:55:28 2026 +0900
 2 files changed, 2 insertions(+)
 create mode 100644 b.txt
 create mode 100644 c.txt
$ git log --oneline
c6b6b4f Add b.txt and c.txt
d73ce48 first

表示の Date: は、元のコミットの日時のままでした。1つのコミットに b.txt と c.txt の両方が入っています。

amend を取り消すには?

amend する前のコミットは、reflog(参照の記録。くわしくは下の「消してしまったら?」)に残ります。手元の git 2.39.3 では、git reflog で commit (amend) の1つ前の行を確かめてから、git reset --soft "HEAD@{1}" を流すと、amend する前のコミットに戻りました。HEAD@{1} は「HEAD の1回前の位置」です。amend で足した c.txt は、git add した状態のまま残りました。

$ git commit --amend -m "add b and c"
[main 3ba0f12] add b and c
 Date: Sat Oct 10 17:57:50 2026 +0900
 2 files changed, 2 insertions(+)
 create mode 100644 b.txt
 create mode 100644 c.txt
$ git reflog -2
3ba0f12 HEAD@{0}: commit (amend): add b and c
5d7fc70 HEAD@{1}: commit: add b
$ git reset --soft "HEAD@{1}"
$ git log --oneline
5d7fc70 add b
ad1c120 first
$ git status --short
A  c.txt

この例は、b.txt をコミットして c.txt を git add した状態から、amend したものです。この手順は、reflog の説明と git reset --soft の説明を組み合わせて、手元の git 2.39.3 で流したものです。amend の取り消しとして公式の文書に書かれた手順は、見つかりませんでした。

コミットを取り消すには?(git reset の --soft・--mixed・--hard)

git reset <コミット> は、今のブランチの先端(HEAD。いま取り出しているコミットを指す名前で、ふつうは今のブランチの先端のコミットを指します)を、指定したコミットに動かします。モードによって、インデックスと作業ツリーも、そのコミットの中身に合わせます。コミットを書かないと HEAD、モードを書かないと --mixed です。動かす前の先端は、ORIG_HEAD に入ります。公式の最新の文書によると、これで commit・merge・rebase・pull などを取り消せます。

push する前のコミットにだけ使います。 公式の文書は、直近の3つのコミットを捨てる例(git reset --hard HEAD~3)に、「そのコミットをもう誰かに渡しているなら、これをしない」と書いています。

3つのモードの違いは、次の表のとおりです(公式の文書の表をもとにしています)。

モードHEAD(コミット)インデックス(add した状態)作業ツリー(ファイル)
--soft指定したコミットに動くそのまま(変更は add 済みのまま残る)そのまま
--mixed(既定)動く新しい HEAD に合わせる(変更は残るが、add していない状態になる)そのまま
--hard動く合わせる新しい HEAD に合わせる。まだコミットしていない変更も消える

HEAD~1 は1つ前のコミット、HEAD~3 は3つ前です(1つ目の親をたどって数えます)。

次の例は、e.txt を足したコミットを取り消すものです(それぞれ、直前のコミットが e.txt を足したコミットの状態から流しました。git 2.39.3 の出力)。

まず --soft です。コミットだけが取り消され、e.txt は git add した状態(A)のまま残りました。

$ git reset --soft HEAD~1
$ git log --oneline -1
4560e07 add d
$ git status --short
A  e.txt

公式の文書の例では、git add した変更が無いときに git reset --soft HEAD~5 のあとで git commit すると、直近の5つのコミットを1つにまとめられます。

次は --mixed(何も付けないときと同じ)です。e.txt は、コミットで足したファイルが、未追跡のファイル(??)に戻りました。

$ git reset HEAD~1
$ git log --oneline -1
4560e07 add d
$ git status --short
?? e.txt

最後は --hard です。e.txt が作業ツリーからも消えました。

$ git reset --hard HEAD~1
HEAD is now at 4560e07 add d
$ git log --oneline -1
4560e07 add d
$ git status --short
$ ls
a.txt
b.txt
c.txt
d.txt

注意: --hard は、まだコミットしていない変更も消します。次の例では、a.txt を書き換えただけ(コミットしていない)の状態で git reset --hard を流すと、書き換えが消えました。

$ git status --short
 M a.txt
?? x.txt
$ git reset --hard
HEAD is now at 52fc3dd add e (3)
$ git status --short
?? x.txt
$ cat a.txt
a

この例では、新しく作った未追跡の x.txt は残りました。ただし、公式の最新の文書は --hard を「未追跡のファイルを上書きすることがある」と書いているので、未追跡のファイルが消えないとは言い切れません。コミットしていない変更は、reflog でも戻せません(下の「消してしまったら?」)。

動かす前に戻したいときは?(ORIG_HEAD)

ORIG_HEAD は、HEAD を大きく動かすコマンド(git am・git merge・git rebase・git reset)が、動かす前の位置を記録するものです。公式の文書によると、動かす前の状態に簡単に戻せます。2つ前のコミットまで --hard で戻したあと、git reset --hard ORIG_HEAD で2つとも戻りました(git 2.39.3 の出力)。--hard なので、その時点でコミットしていない変更は消えます。

$ git reset --hard HEAD~2
HEAD is now at c6b6b4f Add b.txt and c.txt
$ git log --oneline
c6b6b4f Add b.txt and c.txt
d73ce48 first
$ git reset --hard ORIG_HEAD
HEAD is now at 52fc3dd add e (3)
$ git log --oneline
52fc3dd add e (3)
4560e07 add d
c6b6b4f Add b.txt and c.txt
d73ce48 first

コミットを外して、直してからコミットし直すには?

公式の文書の例「Undo a commit and redo」は、直前のコミットが足りなかった・メッセージを打ち間違えたときに使う手順です。git reset --soft HEAD~1 で外し、直して、git commit -c ORIG_HEAD でコミットし直します(文書の例は git commit -a -c ORIG_HEAD)。-c は元のコミットのメッセージを使い回し、エディタが開いて直せます。メッセージを直さないなら -C です。-C は、元のメッセージと作者の情報(日時も)を使い回します。

git 2.39.3 では、c.txt を足したコミット(add c)を外し、c.txt の中身を直して git add したあと、-C で元の件名のままコミットし直せました。

$ git reset --soft HEAD~1
$ git status --short
A  c.txt
$ git commit -C ORIG_HEAD
[main 2ae6a3d] add c
 Date: Sat Oct 10 19:05:23 2026 +0900
 1 file changed, 2 insertions(+)
 create mode 100644 c.txt
$ git log --oneline
5c402df add c
5d7fc70 add b
ad1c120 first

git commit --amend は、この手順のおおよその代わりになります(公式の文書は「reset --soft で外す → 直す → commit -c ORIG_HEAD とおおよそ同じ。ただしマージコミットも amend できる」と書いています)。

push したあとのコミットを取り消すには?(git revert)

push したあと、共有しているブランチでコミットを取り消すときは、ふつうは git revert です。公式の gitfaq は、バグのある変更が main に入ってしまったときの取り消し方として、ふつうは git revert を使う、と書いています。元の変更が入った履歴を残しつつ、それを打ち消す新しいコミットを足すやり方です。

revert は、コミットを消しません。 指定したコミットが入れた変更を打ち消して、それを記録する新しいコミットを足します。元のコミットは履歴に残ります。次の例は、push 済みの bad commit(bad.txt を足したコミット)を revert したものです。リモートは、練習用に手元に作ったもの(../remote.git)です(git 2.39.3 の出力)。

$ git log --oneline
00469ee bad commit
062375d second
a7664e1 first
$ git revert --no-edit HEAD
[main 5944750] Revert "bad commit"
 Date: Sat Oct 10 17:55:42 2026 +0900
 1 file changed, 1 deletion(-)
 delete mode 100644 bad.txt
$ git log --oneline
5944750 Revert "bad commit"
00469ee bad commit
062375d second
a7664e1 first
$ ls
a.txt
$ git push
To ../remote.git
   00469ee..5944750  main -> main

bad commit は履歴に残ったまま、その上に Revert "bad commit" が足され、bad.txt が消えています。revert は履歴を足すだけなので、ふつうの git push で通りました(次の節で説明する fast-forward の更新だからです)。

revert が作るコミットのメッセージは、件名が Revert "<元の件名>"、本文が This reverts commit <元のコミットの40桁の名前>. です(git 2.39.3 の実測)。--no-edit は、メッセージのエディタを開かないオプションです。端末から流すと、ふつうはエディタが開きます。公式の文書は、自動で作るメッセージに加えて、なぜ打ち消すのかを書くことを強く勧めています。revert を revert したときの件名は、git 2.39.3 では Revert "Revert "add x"" でしたが、2.43 からは Reapply "<元の件名>" になります。

revert の前に: コミットしていない変更があると、revert は止まることがあります。先に変更をコミットするか、stash(変更をいったんしまうコマンド。git stash の使い方)で退避してから流します。止まったときは、何も変わりませんでした(くわしくは下の「よくある質問」)。

revert とよく混ぜられるものが2つあります。公式の文書は、コミットしていない変更を全部捨てたいなら git reset(とくに --hard)、ファイルを別のコミットの状態にしたいなら git restore(--source)を見るように、と案内しています。どちらもコミットしていない変更を捨てるので注意が要ります。また、revert は rebase とは別のものです(merge と rebase の違いの記事でも触れています)。

push したあとの reset・amend が危ない理由

なぜ push が断られる?

ブランチをコミット A から B に動かすとき、B が A の子孫なら、fast-forward の更新といいます。履歴を失わない更新です。そうでない更新(non-fast-forward)は履歴を失うので、git push は既定で断ります(公式の文書)。revert は履歴に足すだけなので通りますが、push 済みのコミットを reset で外したり、amend で置き換えたりすると、断られます。

reset の例です。bad commit を push したあと、手元で git reset --hard HEAD~1 して push すると、断られました(git 2.39.3 の出力)。

$ git push
To ../remote.git
   062375d..00469ee  main -> main
$ git reset --hard HEAD~1
HEAD is now at 062375d second
$ git push
To ../remote.git
 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to '../remote.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
$ git status
On branch main
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)

nothing to commit, working tree clean

断られたとき、リモートは変わっていません。手元で reset しただけなら、リモートの履歴はそのままです。

amend の例です。push 済みのコミットを amend して push すると、同じように断られました(git 2.39.3 の出力)。

$ git commit --amend -m "add t"
[main ce67eaf] add t
 Date: Sat Oct 10 17:55:42 2026 +0900
 1 file changed, 1 insertion(+)
 create mode 100644 t.txt
$ git push
To ../remote.git
 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to '../remote.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

案内の文を、そのまま訳さないでください。 amend のあとは、手元にだけあるコミットがあるのに、案内は「手元のブランチの先端がリモートより遅れている(behind)」と出ます。これを「遅れているから pull」と受け取ると、amend の場面には合いません。git push の公式の文書は、この断られ方を、「push 済みなのを忘れて amend して push しようとした」場面として別に説明しています。案内の文は版によっても違い、今の版のソースでは2文目が「リモートの変更を取り込みたいなら、push の前に git pull を使う」になっています。

強制 push をしてよいのは、どんなとき?

git push の公式の文書は、自分で push したコミットを amend した場面について、こう書いています。その間に誰も、元のコミットを取ってきていない(その上で作業を始めていない)と確かなときに限り、git push --force で上書きしてよい。--force は、履歴を失うつもりのときのためのものです。

Pro Git も、amend するのは、まだ手元にあって push していないコミットだけにする、と書いています。push 済みのコミットを amend して強制 push すると、一緒に作業している人が困るからです。共有しているブランチでは、ふつうは revert です(前の節)。

git push --force

注意: このコマンドは、その間に誰も元のコミットを取ってきていないと確かなときだけ使います。--force は、先祖でないものへの更新を断るチェックを外し、リモートのリポジトリのコミットを失わせることがあります。公式の文書は、気をつけて使うように書いています。また --force は、push するすべてのブランチに効きます。1つのブランチだけ強制したいときは、公式の文書によると、ブランチ名の前に + を付けます(例 git push origin +master)。

--force と --force-with-lease の違いは?

--force-with-lease は、リモートのブランチの今の値が「思っている値」と同じときだけ上書きし、違えば失敗するオプションです。ロックせずに「借りる(lease)」ようなもの、と公式の文書は説明しています。値を書かないときは、手元が最後に取ってきたリモートの位置(origin/main など)と、リモートの今の値が同じことを確かめます。公式の文書が挙げる場面は、公開済みのものを rebase し直している間に、別の人が元の履歴の上にコミットを push したときです。そこで --force で押し切ると、その人の作業が失われます。rebase は merge と rebase の違いの記事で説明しています。

誰も push していないときは、--force-with-lease で上書きできました(git 2.39.3 の出力)。

$ git push --force-with-lease
To ../remote.git
 + 3e91d5c...ce67eaf main -> main (forced update)

--force-with-lease が確かめるのは、リモートが動いていないかだけです。使ってよいのは --force と同じく、その間に誰も元のコミットを取ってきていないと確かなときだけです。

別の人(bob)が先に push していたのに、手元で取り込まないまま amend したときは、--force-with-lease は断られ、--force は上書きしました(git 2.39.3 の出力)。

$ git commit --amend -m "add t.txt"
[main d164c41] add t.txt
 Date: Sat Oct 10 17:55:42 2026 +0900
 1 file changed, 1 insertion(+)
 create mode 100644 t.txt
$ git push --force-with-lease
To ../remote.git
 ! [rejected]        main -> main (stale info)
error: failed to push some refs to '../remote.git'
$ git push --force
To ../remote.git
 + 860e1d8...d164c41 main -> main (forced update)

--force のあと、bob の側でリモートの最新を取ってくると(マージはしていない状態で)、bob が push したコミット(bob's work、860e1d8)は、リモートの履歴から消えていました。bob の手元には、リモートに無いコミットが残り、リモートと分かれた状態になりました。

$ git log --oneline origin/main
d164c41 add t.txt
5944750 Revert "bad commit"
00469ee bad commit
062375d second
a7664e1 first
$ git status -sb
## main...origin/main [ahead 2, behind 1]

注意: このように、--force は、その間に別の人が push したコミットを、リモートから消します。その間に誰も取ってきていないと確かなとき以外は使いません。ほかの人の側での直し方は、この記事では扱いません。

--force-with-lease を付ければ間違いが起きない、とは言い切れません。公式の文書は、次のように書いています。

  • 値を書かない --force-with-lease は、裏で git fetch が走る環境(エディタなどが自動で fetch する、cron で git fetch するなど)と相性がとても悪い。さらに、裏のプロセスが ref を更新していると、この確かめは簡単に破られる(原文は "trivially defeated")、とも書いている
  • 値を明示する --force-with-lease=<refname>:<expect> 以外の形は、まだ試験的(experimental)で、意味が変わるかもしれない(2026年10月10日時点の文書)

--force-with-lease は 1.8.5 から、--force-if-includes は 2.30 から使えます(git 2.39.3 にはどちらもあります)。--force-if-includes は、リモート追跡ブランチの先端が手元に取り込まれているときだけ強制するオプションで、値を書かない --force-with-lease と一緒に使います。

また、リモート側の設定で、強制 push そのものが断られることもあります(remote rejected)。公式の文書は、リモート側のフックや、receive.denyNonFastForwards などの設定が原因になる、と書いています。

消してしまったら?(git reflog)

git reset --hard などで、コミットが見えなくなったときは、reflog(参照の記録)で探します。Pro Git は、コミットしたものは、ほぼいつでも取り戻せる、と書いています(消したブランチのコミットや、--amend で置き換えたコミットも)。ただし、一度もコミットしていないものを失うと、まず戻らない、とも書いています。

reflog で探して戻すには?

reflog は、ブランチなどの先端が、この手元のリポジトリでいつ動いたかを記録したものです。git reflog は、指定した参照(書かないと HEAD)の記録を、新しい順に出します。HEAD の記録には、ブランチの切り替えも入ります。git log -g --abbrev-commit --pretty=oneline の別名です(git log は Git コマンドのよく使う使い方の記事で紹介しています)。

HEAD@{2} は「HEAD の2回前の位置」です。表示は <短いハッシュ> HEAD@{<番号>}: <操作>: <説明> の形で、commit:・commit (amend):・reset: moving to HEAD~1 のように、何をしたかも出ます。次は、add e (3) のコミットを git reset --hard HEAD~1 で外したあとの reflog です(git 2.39.3 の出力)。

$ git reflog
4560e07 HEAD@{0}: reset: moving to HEAD~1
52fc3dd HEAD@{1}: commit: add e (3)
4560e07 HEAD@{2}: reset: moving to HEAD~1
9db9ef3 HEAD@{3}: commit: add e (again)
4560e07 HEAD@{4}: reset: moving to HEAD~1
5986098 HEAD@{5}: commit: add e
4560e07 HEAD@{6}: commit: add d
c6b6b4f HEAD@{7}: commit (amend): Add b.txt and c.txt
b3b4ae6 HEAD@{8}: commit (amend): add b and c
d7aa32f HEAD@{9}: commit: add b and c
d73ce48 HEAD@{10}: commit (initial): first

HEAD@{1} の add e (3) が、外したコミットです。git reset --hard "HEAD@{1}"(または見つけたハッシュ)で戻せました(git 2.39.3 の出力)。--hard なので、その時点でコミットしていない変更も消えます。 先に git status で確かめ、変更があれば、コミットか stash してから流します。

$ git reset --hard "HEAD@{1}"
HEAD is now at 52fc3dd add e (3)
$ git log --oneline
52fc3dd add e (3)
4560e07 add d
c6b6b4f Add b.txt and c.txt
d73ce48 first
$ ls
a.txt
b.txt
c.txt
d.txt
e.txt

この戻し方は、reflog の説明と git reset の説明を組み合わせて、手元の git 2.39.3(macOS の bash)で流したものです。公式の文書の例には、そのままの形はありません。"HEAD@{1}" の引用符つきの書き方が、Windows のコマンドプロンプトや PowerShell でそのまま使えるかは、確かめていません。

Pro Git は、戻したいコミットのハッシュを reflog で見つけて、そこに新しいブランチを作る方法も紹介しています。ブランチが付けば、そのコミットは、ブランチからたどれるようになります。git branch <名前> <ハッシュ> は作業ツリーに触らないので、コミットしていない変更を消したくないときは、こちらの方法にします(git 2.39.3 の出力)。

$ git reset --hard HEAD~1
HEAD is now at 4560e07 add d
$ git reflog -3
4560e07 HEAD@{0}: reset: moving to HEAD~1
52fc3dd HEAD@{1}: reset: moving to ORIG_HEAD
c6b6b4f HEAD@{2}: reset: moving to HEAD~2
$ git branch recover-branch 52fc3dd
$ git log --oneline recover-branch -2
52fc3dd add e (3)
4560e07 add d

reflog でも戻せないものと、期限は?

コミットしていない変更は、reflog に出ません。 reflog に載るのは、コミットなど、参照の動きだけです。次の例では、a.txt の書き換え(コミットしていない)を git reset --hard で捨てたあとの reflog に、reset: moving to HEAD の行だけがあり、書き換えの記録はありませんでした(git 2.39.3 の出力)。git restore で捨てた変更も、Pro Git は戻らないと書いています(前の「add していない変更を捨てるには?」)。

$ git reflog -2
52fc3dd HEAD@{0}: reset: moving to HEAD
52fc3dd HEAD@{1}: reset: moving to 52fc3dd

reflog の記録には、期限があります。 git reflog expire は、既定で90日(gc.reflogExpire)より古い記録を消します。今の先端からたどれない記録は、既定で30日(gc.reflogExpireUnreachable)です。たどれない記録とは、git commit --amend や git rebase の前のコミットなどです。git reset --hard で外したコミットも、今の先端からたどれないので、この30日の記録に当たります。公式の文書は、これらは早めに消したい人が多いので既定が短い、と書いています。古い記録を消すのは、ふつうは git gc の役目です。git gc は、オブジェクトを作るふだんの操作のあとで、リポジトリが大きくなっていれば自動でも走ります。期限が来たときの動きまでは、確かめていません。

また、reflog は、この手元のリポジトリでの記録です。ほかの人の手元での戻し方は、この記事では扱いません。

よくある質問

push する前のマージを取り消すには?

公式の文書の例「Undo a merge or pull」では、git pull がコンフリクトだらけになったときは、git reset --hard(git reset --hard HEAD と同じ)で、インデックスと作業ツリーのごちゃごちゃを片づけます。マージしたあとでやめたくなったときは、git reset --hard ORIG_HEAD です。git pull や git merge は、元の先端を ORIG_HEAD に残すので、そこに --hard で戻すと、インデックスと作業ツリーも、ブランチの先端も、マージの前に戻ります。ただし、手元のコミットしていない変更も捨ててしまうので、残したいときは git reset --merge ORIG_HEAD を使う、と公式の文書は書いています(この記事では流していません)。マージについては、merge と rebase の違いの記事で説明しています。

push 済みのマージを取り消すには?

git revert を使いますが、マージコミットは、そのままでは revert できません。どちらの親を本流とみるか分からないからです。-m <親の番号>(1から数えます)で、本流の親を指定します。次の例では、-m を付けないとエラーになり、-m 1 を付けると、マージで入った f.txt が消えました(git 2.39.3 の出力)。

$ git revert --no-edit HEAD
error: commit 1d8bea08aedd8a1884de4350fd4b1d61ec0e5552 is a merge but no -m option was given.
fatal: revert failed
$ git revert --no-edit -m 1 HEAD
[main f86bd03] Revert "Merge branch 'feature'"
 Date: Sat Oct 10 17:55:55 2026 +0900
 1 file changed, 1 deletion(-)
 delete mode 100644 f.txt

注意: 公式の文書によると、マージを revert すると、「そのマージが入れた変更は今後要らない」と宣言したことになります。そのため、あとで同じブランチをもう一度マージしても、revert したマージに含まれていたコミットの変更は入ってきません。公式の文書は、これが望みどおりかどうかは場合による、と書いています(この動きは、この記事では流していません)。

複数のコミットをまとめて revert するには?

git revert HEAD~3 は、HEAD から数えて4番目(HEAD の3つ前)のコミットを打ち消す新しいコミットを作ります(公式の文書の例)。複数のコミットの打ち消しを1つにまとめたいときは、-n(--no-commit)を付けます。打ち消す変更を作業ツリーとインデックスに当てるだけで、コミットは作りません。公式の文書の例は、範囲を指す git revert -n master~5..master~2 です。次の例は、2つのコミットを指定して -n で打ち消し、自分で1つのコミットにしたものです(-q は、表示を減らすオプションです。git 2.39.3 の出力)。

$ git revert -n HEAD HEAD~1
$ git status --short
D  x.txt
D  y.txt
$ git log --oneline -1
5f25e21 add y
$ git commit -q -m "Revert add x and add y"
$ git log --oneline
ac7e811 Revert add x and add y
5f25e21 add y
608fc63 add x
a5bb1a9 first

revert でエラーが出たら?

出方は、おもに3つです。

1. コミットしていない変更があって止まる。 公式の文書は、revert には作業ツリーがきれい(HEAD から変更が無い)なことが要る、と書いています。手元の git 2.39.3 では、場合によって分かれました。git add した変更があると、関係ないファイルでも止まりました。git add していない変更が、打ち消すコミットと同じファイルにあるときも止まりました。関係ないファイルにあるだけなら通り、変更は残りました。どの場合も、止まったときは何も変わりませんでした。止まることがあるので、先にコミットするか stash します。

$ git status --short
M  a.txt
$ git revert --no-edit HEAD
error: your local changes would be overwritten by revert.
hint: commit your changes or stash them to proceed.
fatal: revert failed

2. マージコミットを revert しようとした。 is a merge but no -m option was given と出たら、上の「push 済みのマージを取り消すには?」のとおり、-m を付けます。

3. コンフリクトした。 打ち消す変更と、あとのコミットの変更が同じ所にあると、コンフリクト(同じ所の変更どうしがぶつかること)で止まります(git 2.39.3 の出力)。

$ git revert --no-edit HEAD~1
Auto-merging a.txt
CONFLICT (content): Merge conflict in a.txt
error: could not revert bb8e5c3... change to two
hint: After resolving the conflicts, mark them with
hint: "git add/rm <pathspec>", then run
hint: "git revert --continue".
hint: You can instead skip this commit with "git revert --skip".
hint: To abort and get back to the state before "git revert",
hint: run "git revert --abort".

直したら、git add(または git rm)して git revert --continue です。このコミットを飛ばすなら --skip、やめて revert の前に戻すなら --abort です。手元の git 2.39.3 では、git revert --abort のあと、a.txt は元の内容(three)に戻り、コミットも増えていませんでした。

まとめ

  • restore は作業ツリーのファイルを戻し、reset はブランチの先端を動かし、revert は打ち消す新しいコミットを足す
  • push の前は、git restore で変更を捨てる(捨てた変更は戻らない、と Pro Git は書いている)、git restore --staged か git reset <ファイル> で git add を取り消す、git commit --amend で直前のコミットを直す、git reset の3つのモードでコミットを外す。--hard は、まだコミットしていない変更も消す
  • push の後は、共有しているブランチなら git revert。元のコミットは消えず、打ち消すコミットを足すので、ふつうの push で通る
  • push 済みのコミットを reset や amend すると、push は断られる。強制 push は、その間に誰も取ってきていないと確かなときだけ。--force-with-lease を付けても、間違いが起きないとは言い切れない
  • 消してしまったコミットは、reflog で探して戻せることがある。ただし、コミットしていない変更は出ず、記録には期限がある(既定で90日。今の先端からたどれない記録は30日で、reset --hard で外したコミットもこれに当たる)

参考資料

※この記事は、公式の資料をもとに AI(Claude)で下書きし、2026年10月10日時点の公式の資料と照らして確かめてから公開しています。誤りに気づいたらお問い合わせからお知らせください。

PR
PR
タイトルとURLをコピーしました