git reset — 付箋ごと戻す(--soft・--mixed・--hard)

git reset は、HEAD が乗っているブランチの付箋を、指定したコミットへ動かすコマンドです。付箋を動かしたあと、ステージと作業ツリーをどこまで付箋に合わせて戻すかを、--soft / --mixed / --hard の3つから選びます。この回では、前の回の checkout との違いから入り、3つのオプションの差を「3つの場所のどれが戻るか」で見ていきます。最後に、reset で消えたように見えるコミットを取り戻す方法と、許可画面での見分け方を扱います。

画面の見方
  • 上のターミナルは、実際に打ったときと同じ出力です(Git は英語で答えます)
  • 下の中で起きていることは、コマンドの結果、Git の中の何が変わったかを図にしたものです。丸がコミット、黒い札がブランチ、ピンクの札が HEAD です
  • 右の説明をスクロールして読み進めると、左のターミナルでコマンドが1つずつ打たれます。→ キーでも1つずつ進みます(← で1つ戻ります。スマホでは「次へ」ボタン)。最後まで進んでから → を押すと次の区間へ移ります。黄色く光っているのが、そのコマンドで変わった場所です
  • どのブランチからも辿れなくなったコミットは、グラフに薄く(点線で)残ります

1. reset は付箋そのものを動かす — checkout との違い

前の回で見たとおり、git checkout は付箋を動かさず、HEAD を付け替えるコマンドでした。ブランチの付箋はそのまま残るので、何も失われません。

git reset は逆です。HEAD はブランチに乗ったままで、HEAD が乗っているブランチの付箋そのものを動かします。同じ「1つ前に戻る」でも、どの付箋が動くかがまったく違います。

コマンド動く付箋ブランチの付箋
git checkout HEAD~1HEAD だけ(detached HEAD になる)元のコミットに残る
git reset --hard HEAD~1HEAD が乗っているブランチ(HEAD も一緒についていく)1つ前のコミットへ動く

checkout と reset — どの付箋が動くか

  1. main は「バターを追加」を指していて、HEAD は main に乗っています。書きかけの変更はありません。ここから「1つ前に戻る」を2通りでやってみます。

  2. まず前の回の checkout です。HEAD だけ が1つ前のコミットへ移り、detached HEAD になりました。main の付箋は「バターを追加」に貼られたままです。

    $ git checkout HEAD~1
    Note: switching to 'HEAD~1'.
    
    You are in 'detached HEAD' state. You can look around, make experimental
    changes and commit them, and you can discard any commits you make in this
    state without impacting any branches by switching back to a branch.
    
    If you want to create a new branch to retain commits you create, you may
    do so (now or later) by using -c with the switch command. Example:
    
      git switch -c <new-branch-name>
    
    HEAD is now at e5c0a62 パンを追加
  3. main に乗り直すと、作業ツリーも「バターを追加」の中身に戻ります。checkout は付箋を1枚も動かしていません。

    $ git checkout main
    Previous HEAD position was e5c0a62 パンを追加
    Switched to branch 'main'
  4. 次は reset です。HEAD は main に乗ったまま、main の付箋そのもの が1つ前へ動きました。作業ツリーの中身は、さっきの checkout と同じ「パンを追加」の時点です。

    $ git reset --hard HEAD~1
    HEAD is now at e5c0a62 パンを追加
  5. main から辿れるのは3つだけになりました。「バターを追加」はグラフに薄く残っています。どのブランチからも辿れなくなっただけで、コミットそのものはまだあります。

    $ git log --oneline
    e5c0a62 (HEAD -> main) パンを追加
    8b41d07 卵を追加
    3f2a9c1 牛乳を追加

reset のあと、git log には「バターを追加」が出なくなりました。ただしコミットが削除されたわけではありません。どの付箋からも辿れなくなっただけです。取り戻し方は7節で扱います。

2. どこまで戻すか — --soft・--mixed・--hard

reset は、まず付箋を動かします。ここは3つとも同じです。違うのは、そのあとステージと作業ツリーも付箋に合わせて戻すかどうかです。

オプションコミット(付箋)ステージ作業ツリー
--soft戻るそのままそのまま
--mixed(何も付けないときの既定)戻る戻るそのまま
--hard戻る戻る戻る

3つの場所の並び(コミット → ステージ → 作業ツリー)で、soft はコミットだけ、mixed はステージまで、hard は作業ツリーまで戻ると覚えてください。作業ツリーに手を出すのは --hard だけです。

3. --soft — 直前のコミットをやり直す

git reset --soft HEAD~1 は、付箋だけを1つ前へ戻し、ステージと作業ツリーはそのまま残す使い方です。直前のコミットの中身がステージに載った状態、つまり「コミットする直前」に戻ります。そこで足りなかった変更を足して、コミットし直します。

Claude Code も、過去の作業ログで git reset --soft HEAD~1 を10回打っています。コミットしたあとに入れ忘れや入れすぎに気づき、コミットを作り直すときの使い方です。

git reset --soft HEAD~1 — 直前のコミットをやり直す

  1. 「パンを追加」をコミットした直後に、ジャムも一緒に入れるはずだったと気づきました。このコミットを作り直します。

    $ git log --oneline
    e5c0a62 (HEAD -> main) パンを追加
    8b41d07 卵を追加
    3f2a9c1 牛乳を追加
  2. --soft は 付箋だけ を動かします。main は「卵を追加」に戻りましたが、ステージと作業ツリーには「パンを買う」が残っています。端末には何も出ません。

    $ git reset --soft HEAD~1
  3. 「パンを買う」の変更が ステージに載ったまま です。コミットする直前の状態に戻った、ということです。

    $ git status
    On branch main
    Changes to be committed:
      (use "git restore --staged <file>..." to unstage)
    	modified:   todo.txt
    
  4. 忘れていた1行を足します。

    $ echo "ジャムを買う" >> todo.txt
  5. ステージに載せます。

    $ git add todo.txt
  6. 作り直したコミットができました。親は「卵を追加」です。

    $ git commit -m "パンとジャムを追加"
    [main 2d0559a] パンとジャムを追加
     1 file changed, 2 insertions(+)
  7. 履歴の上では、最初からこのコミットだったように見えます。元の「パンを追加」は、どのブランチからも辿れなくなって、グラフに薄く残っています。

    $ git log --oneline
    2d0559a (HEAD -> main) パンとジャムを追加
    8b41d07 卵を追加
    3f2a9c1 牛乳を追加

新しいコミットは、元の「パンを追加」を書き換えたものではありません。別の新しいコミットです。元のコミットは、付箋から外れてグラフに薄く残っています。直前のコミットを作り直すだけなら git commit --amend という書き方もあり、こちらも同じように「新しいコミットを作って付箋を付け替える」動きをします。

4. --mixed(既定)— ステージも戻す

オプションを付けずに git reset HEAD~1 と打つと、--mixed になります。付箋を動かし、ステージも付箋の先のコミットの中身に戻します。作業ツリーには触りません。

git reset HEAD~1(--mixed)— ステージも戻す

  1. オプションを付けない reset は --mixed です。main が1つ前へ動き、ステージも その中身に戻りました。作業ツリーに残った「バターを買う」を、Unstaged changes after reset の行が知らせています。

    $ git reset HEAD~1
    Unstaged changes after reset:
    M	todo.txt
  2. 今度は「バターを買う」が ステージに載っていない 変更として出ます。--soft との違いはここだけです。

    $ git status
    On branch main
    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:   todo.txt
    
    no changes added to commit (use "git add" and/or "git commit -a")
  3. 作業ツリーとステージの差は「バターを買う」の1行です。書いた内容は何も失われていません。

    $ git diff
    diff --git a/todo.txt b/todo.txt
    --- a/todo.txt
    +++ b/todo.txt
    +バターを買う

--soft との違いは、取り消したコミットの変更が「ステージに載っている」か「載っていない」かだけです。どちらも作業ツリーのファイルは消えません。--mixed で戻したあとは、git add をやり直して、必要な分だけコミットし直せます。

5. --hard — 作業ツリーまで戻す

--hard は、付箋を動かしたうえで、ステージと作業ツリーの両方を付箋の先のコミットの中身で上書きします。コミットしていない変更は、add 済みかどうかに関係なく消えます。

git reset --hard — 作業ツリーまで戻す

  1. todo.txt に書きかけの「醤油を買う」があります。一度も add していない notes.txt もあります。

    $ git status
    On branch main
    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:   todo.txt
    
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
    	notes.txt
    
    no changes added to commit (use "git add" and/or "git commit -a")
  2. もう1つ、新しいファイル recipe.txt を作ります。

    $ echo "じゃがいも 玉ねぎ 牛肉" > recipe.txt
  3. こちらは add だけしておきます。コミットはしていません。

    $ git add recipe.txt
  4. --hard は付箋を動かし、ステージと作業ツリーの両方 をその中身で上書きします。HEAD is now at の行に、戻った先のコミットが出ます。

    $ git reset --hard HEAD~1
    HEAD is now at e5c0a62 パンを追加
  5. 「バターを買う」と一緒に、書きかけの「醤油を買う」も消えました。この書きかけは一度もコミットしていないので、もう取り戻せません。

    $ cat todo.txt
    牛乳を買う
    卵を買う
    パンを買う
  6. notes.txt は残っています。Git が追跡していないファイルには、reset --hard は手を出しません。一方、add した recipe.txt は追跡中だったので、作業ツリーからも消えました。

    $ git status
    On branch main
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
    	notes.txt
    
    nothing added to commit but untracked files present (use "git add" to track)

この場面で起きたことを整理すると、次のとおりです。

reset --hard の前の状態reset --hard のあと取り戻せるか
コミット「バターを追加」付箋から外れる取り戻せる(7節の reflog)
書きかけの「醤油を買う」(コミットしていない)消える取り戻せない
add しただけの新しいファイル recipe.txt消えるふつうの操作では取り戻せない(下の注)
一度も add していないファイル notes.txtそのまま残る(消えていない)

コミットしていない変更は、reset --hard で消えると reflog でも戻せません。reflog が覚えているのはコミットだけで、書きかけの変更はそこに載らないからです。逆に、Git が一度も追跡していないファイル(一度も add していないファイル)には手を出しません。ただし、add しただけの新しいファイルは「追跡中」なので消えます。

注: add した時点の中身は、Git の内部に部品として残っていることがあり、専門的な調べ方で拾える場合があります。ただし、ふつうのコマンドでは戻せないので、消えるものとして扱ってください。一度も add していない書きかけは、Git のどこにも記録されていないので、本当に戻せません。

git reset --hard(コミットの指定なし)

コミットを書かずに git reset --hard と打つと、行き先は HEAD(今いるコミット)になります。付箋は動きませんが、ステージと作業ツリーは HEAD の中身に上書きされます。つまり「コミットしていない変更を、全ファイルまとめて捨てる」コマンドです。許可画面で見かけたら、書きかけが残っていないかを必ず確かめてください。

6. git reset HEAD ファイル — add を取り消す

reset の後ろにファイル名を書くと、働きが変わります。付箋は動かさず、そのファイルのステージだけを HEAD の中身に戻す、つまり add の取り消しです。作業ツリーのファイルには触りません。

Claude Code が打った reset のうち、いちばん多いのがこの形です(git reset -q HEAD ファイル が28回)。-q は「何も表示しない」という指定で、動きは変わりません。コミットに入れるつもりのないファイルまで add してしまったとき、そのファイルだけをステージから外すために打っています。

git reset HEAD ファイル — add を取り消す

  1. todo.txt に1行足します。

    $ echo "ジャムを買う" >> todo.txt
  2. メモ用の notes.txt も作ります。

    $ echo "特売は水曜" > notes.txt
  3. 両方を add しました。ところが notes.txt は、コミットに入れるつもりがなかったとします。

    $ git add todo.txt notes.txt
  4. reset の後ろにファイル名を書くと、付箋は動かさず、そのファイルの ステージだけ を HEAD の中身に戻します。-q は「何も表示しない」という指定です。

    $ git reset -q HEAD notes.txt
  5. notes.txt はステージから外れ、追跡していないファイルに戻りました。作業ツリーのファイルはそのままです。

    $ git status
    On branch main
    Changes to be committed:
      (use "git restore --staged <file>..." to unstage)
    	modified:   todo.txt
    
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
    	notes.txt
    
  6. 同じことは git restore --staged でもできます。前の回で見た、add の取り消しの新しい書き方です。

    $ git restore --staged todo.txt
  7. todo.txt もステージから外れました。作業ツリーの「ジャムを買う」は残っています。

    $ git status
    On branch main
    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:   todo.txt
    
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
    	notes.txt
    
    no changes added to commit (use "git add" and/or "git commit -a")
  8. ファイル名を書いたときに使えるのは --mixed だけです。--hard や --soft を付けると、Git は止まります。

    $ git reset --hard HEAD todo.txt
    fatal: Cannot do hard reset with paths.

前の回で見た git restore --staged ファイル は、この使い方を分けて名前を付けたコマンドです。ファイルを指定したときは、どちらも同じ結果になります。

書き方付箋ステージ作業ツリー
git reset HEAD ファイル(git reset ファイル も同じ)動かないそのファイルだけ HEAD の中身に戻るそのまま
git restore --staged ファイル動かないそのファイルだけ HEAD の中身に戻るそのまま

ファイル名を書いた reset で使えるのは --mixed だけです。--hard や --soft を付けると fatal: Cannot do hard reset with paths. のように止まります。1つのファイルを作業ツリーごと戻したいときは、前の回の git checkout HEAD -- ファイル か git restore --staged --worktree ファイル を使います。

7. 消えたように見えるコミットは reflog で取り戻せる

reset で付箋から外れたコミットは、git log には出なくなります。それでも、コミットそのものはまだリポジトリに残っています。行き先を知る手がかりが reflog です。reflog は、HEAD がこれまでに指してきたコミットを、新しい順に記録したものです。

  • HEAD@{0} は今いる場所、HEAD@{1} は1つ前にいた場所です
  • reset の直後なら、HEAD@{1} が「reset する前にいたコミット」です
  • git reset --hard HEAD@{1} で、付箋をそこへ戻せます

reflog で取り戻す — git reset --hard HEAD@{1}

  1. 「バターを買う」を1行足します。

    $ echo "バターを買う" >> todo.txt
  2. コミットします。main は「バターを追加」を指しています。

    $ git commit -am "バターを追加"
    [main fbb4973] バターを追加
     1 file changed, 1 insertion(+)
  3. 1つだけ戻すつもりが、2つ戻してしまいました。main は「卵を追加」を指しています。

    $ git reset --hard HEAD~2
    HEAD is now at 8b41d07 卵を追加
  4. git log には2つしか出ません。でもグラフには、「パンを追加」と「バターを追加」が薄く残っています。

    $ git log --oneline
    8b41d07 (HEAD -> main) 卵を追加
    3f2a9c1 牛乳を追加
  5. git reflog は HEAD が指してきたコミットの記録 です。HEAD@{0} が今いる場所、HEAD@{1} が1つ前にいた場所です。HEAD@{1} の行が、さっきのコミット「バターを追加」です。

    $ git reflog -2
    8b41d07 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~2
    fbb4973 HEAD@{1}: commit: バターを追加
  6. HEAD@{1} へ付箋を戻します。main は再び「バターを追加」を指しました。

    $ git reset --hard HEAD@{1}
    HEAD is now at fbb4973 バターを追加
  7. 4つとも戻りました。reset で消えたように見えたコミットは、付箋が外れていただけ です。

    $ git log --oneline
    fbb4973 (HEAD -> main) バターを追加
    e5c0a62 パンを追加
    8b41d07 卵を追加
    3f2a9c1 牛乳を追加

reflog の行には「何をしてそこへ来たか」が書かれています。reset: moving to HEAD~2 は「reset で HEAD~2 へ動いた」、commit: バターを追加 は「コミットしてそこへ進んだ」という意味です。reset のあとに別の操作をすると番号がずれるので、HEAD@{1} と決め打ちせず、まず git reflog で行き先を確かめてから戻してください。前の回のように git branch 名前 HEAD@{1} で付箋を貼っても取り戻せます。

reflog で戻せるもの・戻せないもの

reflog が覚えているのは、HEAD が指したことのあるコミットだけです。一度もコミットしなかった書きかけの変更は、reflog には載りません。また、reflog の記録は永久ではなく、既定では一定期間(どこからも辿れないものは30日)を過ぎると、片付けのときに消されることがあります。取り戻すなら早めに、です。

8. Claude Code の許可画面で見かけたら

reset は、この講座で扱うコマンドの中でも、許可する前にいちばん注意して読みたいものです。分かれ目は--hard が付いているかどうかです。--soft と --mixed は作業ツリーに触らないので、書いたファイルは消えません。--hard は、コミットしていない変更をまとめて消し、その変更はどうやっても戻せません。

コマンド何が起きるか消えうるもの
git reflog / git log / git status見るだけなし
git reset -q HEAD ファイル / git restore --staged ファイルそのファイルの add を取り消す(作業ツリーはそのまま)注意 add した後にさらに書き換えていたら、add した時点の版(ステージにだけあった中身)
git reset --soft HEAD~1付箋だけを1つ戻す。変更はステージに残る注意 直前のコミットが付箋から外れる(reflog で取り戻せる)
git reset HEAD~1 / git reset --mixed HEAD~1付箋とステージを戻す。作業ツリーはそのまま注意 直前のコミットが付箋から外れる(reflog で取り戻せる)。ステージにだけあった中身
git reset --hard(行き先なし)付箋は動かさず、ステージと作業ツリーを HEAD の中身で上書き未コミットの変更すべて(add しただけの新しいファイルも)
git reset --hard HEAD~1 / git reset --hard コミットID付箋を動かし、ステージと作業ツリーもその中身で上書き未コミットの変更すべて。外れたコミットは reflog で取り戻せる
git reset --hard HEAD@{1}取り戻すための reset。中身は上と同じ --hard未コミットの変更すべて

--hard の許可を求められたら、先に git status の結果を見てください。Changes not staged for commit や Changes to be committed に自分の書きかけが並んでいたら、それが消えるものです。残したいなら、先にコミットするか、--soft / --mixed で済まないかを Claude Code に聞き返します。一度も add していないファイル(Untracked files に出るもの)は、reset --hard では消えません。

次の回では、付箋を戻すのではなく「打ち消すコミットを足す」git revert を扱います。

まとめ

  • git reset は、HEAD が乗っているブランチの付箋そのものを動かす。checkout は付箋を動かさず、HEAD を付け替える
  • --soft はコミットだけ、--mixed(既定)はステージまで、--hard は作業ツリーまで戻す
  • git reset --soft HEAD~1 は「直前のコミットをやり直す」使い方。変更はステージに残る
  • git reset HEAD ファイル は add の取り消しで、git restore --staged ファイル と同じ結果になる
  • reset --hard はコミットしていない変更を消し、それは reflog でも戻せない。一度も add していないファイルは残る
  • 付箋から外れたコミットは消えていない。git reflog で探して git reset --hard HEAD@{1} で戻せる