reflog で取り戻す — 消えたように見えるコミット

rebase で作り直したとき、git branch -D で剥がしたとき、reset --hard で戻しすぎたとき。git log から消えたコミットは、まだ手元に残っています。手がかりは、HEAD が通った場所の記録である git reflog です。1-5 の reset の回で見た一番短い使い方の先として、1行の読み方、取り戻し方の2つ(付箋を貼り直すか、付箋ごと戻すか)、3つの場面での探し方、reflog でも取り戻せないものを見て、第3部を締めくくります。

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

1. おさらい — reflog は HEAD の足あと

1-5 git resetでは、reset --hard HEAD~2 で戻しすぎたあと、git reflog で1つ前の場所を確かめて git reset --hard HEAD@{1} で戻しました。reflog(リフログ)は、HEAD がいたコミットを新しい順に並べた記録で、HEAD@{0} が今、HEAD@{1} が1つ前です。

行が増えるのは HEAD が動いたときです。commit・switch・reset・merge・rebase・pull のたびに1行ずつ積まれます。push・fetch・git branch -D のように HEAD を動かさない操作では増えません。そして、reflog の行が指しているコミットは、付箋から外れていても Git が掃除しません。「消えた」と思ったら、まず git reflog です。

2. reflog の1行を読む

1行は、b365067 HEAD@{2}: rebase (pick): メモを作る のように3つの部分でできています。先頭の ID はその操作のあとで HEAD がいたコミット、HEAD@{2} は今から数えて何回前か、コロンの後ろはどの操作でそこへ来たかです。操作の書き方は決まっています。

reflog の書き方何が起きたか
commit: バターを追加コミットして進んだ(commit (amend) は直前のコミットの作り直し)
checkout: moving from main to featuremain から feature へ乗り換えた。git switch でもこう書かれる
reset: moving to HEAD~2reset で付箋を動かした
merge feature: Fast-forward早送りで取り込んだ。マージコミットを作ったときは Merge made by the 'ort' strategy.(3-1)
rebase (start): checkout mainrebase を始めた。この上の行は作り直したコミット(3-2)
rebase (pick): メモを作るコミットを1つ作り直した
rebase (finish): returning to refs/heads/featurerebase が終わり、feature を新しい先頭へ付け替えた
pull --ff-only: Fast-forwardgit pull --ff-only で早送りした(2-3)

番号には落とし穴があります。番号は「今から何回前か」なので、操作のたびに1つずつずれます。取り戻すコマンドを打つ直前に git reflog を見直すか、番号の代わりに先頭の ID を書いてください。

PowerShell で打つとき

PowerShell では { } が特別な意味を持つので、git reset --hard HEAD@{1} は fatal: Needed a single revision で止まります。'HEAD@{1}' と引用符で囲んでください。Claude Code が PowerShell で打って同じエラーになったときも、原因はこれです。

3. 取り戻し方は2つ — 付箋を貼り直すか、付箋ごと戻すか

コマンド何が動くか今いるブランチ書きかけ
git branch 名前 HEAD@{n}見つけたコミットに新しい付箋を1枚貼るそのままそのまま
git reset --hard HEAD@{n}今のブランチの付箋をそこへ動かす付箋が動き、それまでの先頭は外れる消える

迷ったら、まず git branch で付箋を貼ります。何も消さずに、薄く残っていたコミットを付箋から辿れるようにするだけだからです。git log --oneline --all で見比べ、今のブランチを戻すと決めてから reset に進んでも遅くありません。そのときは先に git status で書きかけが無いことを確かめます。

4. rebase をやめて元に戻す

rebase はコミットを相手の先へ作り直すので、ID が変わり、元のコミットは置き去りになります(3-2)。終わったあとで「前の形がよかった」となったら、reflog で rebase の前の場所を探します。

rebase をやめて元に戻す — rebase (start) の1つ下

  1. feature は、main の「卵を追加」から分かれたブランチです。ここでメモのコミットを2つ作ります。

    $ git switch feature
    Switched to branch 'feature'
  2. 新しいファイル notes.txt を作ります。

    $ echo "牛乳は低脂肪" > notes.txt
  3. ステージに載せます。

    $ git add notes.txt
  4. 1つ目のコミット 10e0cad ができました。

    $ git commit -m "メモを作る"
    [feature 10e0cad] メモを作る
     1 file changed, 1 insertion(+)
     create mode 100644 notes.txt
  5. メモにもう1行足します。

    $ echo "卵はLサイズ" >> notes.txt
  6. 2つ目のコミット 2cd8380 ができました。feature は main と枝分かれしています。

    $ git commit -am "メモを足す"
    [feature 2cd8380] メモを足す
     1 file changed, 1 insertion(+)
  7. 3-2 の回で見た rebase です。2つのコミットが main の先に、新しい ID b365067・1cce4ff で作り直されました。元の2つは付箋から外れ、グラフに薄く残っています。

    $ git rebase main
    Successfully rebased and updated refs/heads/feature.
  8. ここで「やっぱり rebase する前に戻したい」となったとします。下から commit → rebase (start) → rebase (pick) が2つ → rebase (finish) の順で、rebase する前にいた場所は rebase (start) の1つ下の行、HEAD@{4} の 2cd8380 です。

    $ git reflog -5
    1cce4ff (HEAD -> feature) HEAD@{0}: rebase (finish): returning to refs/heads/feature
    1cce4ff HEAD@{1}: rebase (pick): メモを足す
    b365067 HEAD@{2}: rebase (pick): メモを作る
    e5c0a62 HEAD@{3}: rebase (start): checkout main
    2cd8380 HEAD@{4}: commit: メモを足す
  9. まず、元のコミットに付箋 before-rebase を貼ります。今いる feature には触らないので、何も消えません。薄かった2つのコミットが、付箋から辿れるようになりました。

    $ git branch before-rebase HEAD@{4}
  10. --all で見ると、rebase の後の2つと前の2つが両方並びます。どちらを残すかは、見比べてから決められます。

    $ git log --oneline --all
    1cce4ff (HEAD -> feature) メモを足す
    b365067 メモを作る
    2cd8380 (before-rebase) メモを足す
    10e0cad メモを作る
    e5c0a62 (main) パンを追加
    8b41d07 卵を追加
    3f2a9c1 牛乳を追加
  11. feature そのものを rebase の前に戻すなら、git reset --hard で付箋を動かします。今度は rebase で作ったほうの2つが薄くなりました。書きかけが無いことを git status で確かめてから打つコマンドです。

    $ git reset --hard before-rebase
    HEAD is now at 2cd8380 メモを足す
  12. feature と before-rebase が同じ 2cd8380 を指し、rebase する前の形に戻りました。before-rebase は、要らなくなったら git branch -d で剥がせます。

    $ git log --oneline --all
    2cd8380 (HEAD -> feature, before-rebase) メモを足す
    10e0cad メモを作る
    e5c0a62 (main) パンを追加
    8b41d07 卵を追加
    3f2a9c1 牛乳を追加

rebase (pick) の行は作り直したコミットの数だけ増えますが、rebase (start) の1つ下が「rebase を打つ直前にいた場所」なのはいつも同じです。コンフリクトで止まっている途中なら、3-2 の git rebase --abort で戻れます。

Claude Code は git rebase --autostash を延べ22回打っていました。rebase のあとで様子がおかしければ、「rebase の前の位置に before-rebase という付箋を貼って」と頼むのが安全です。

5. -D で剥がしたブランチを取り戻す

1-3 ブランチと HEADでは、取り込んでいないコミットがあるブランチを、-d は断り、-D は確認せずに剥がすのを見ました。剥がすと、そのブランチ自身の reflog(git reflog show menu)も一緒に消えます。それでも手がかりは2つ残ります。1つ目は Deleted branch menu (was d24492c). の was の後ろの ID、2つ目が reflog です。

-D で剥がしたブランチを貼り直す

  1. 献立を試しに書くブランチ menu を作って乗ります。

    $ git switch -c menu
    Switched to a new branch 'menu'
  2. 新しいファイル menu.txt を作ります。

    $ echo "月曜はカレー" > menu.txt
  3. ステージに載せます。

    $ git add menu.txt
  4. menu だけが1つ先へ進みました。「献立を書く」は、menu の付箋からしか辿れません。

    $ git commit -m "献立を書く"
    [menu d24492c] 献立を書く
     1 file changed, 1 insertion(+)
     create mode 100644 menu.txt
  5. main に戻ります。乗り換えるたびに、reflog には checkout の行が増えます。

    $ git switch main
    Switched to branch 'main'
  6. もう要らないと思って、-D で付箋を剥がしました。(was d24492c) は「剥がす前は d24492c を指していた」という意味で、この ID がそのまま手がかりになります。「献立を書く」は薄くなりました。

    $ git branch -D menu
    Deleted branch menu (was d24492c).
  7. --all でも「献立を書く」は出てきません。menu の付箋が無くなったので、ブランチから辿る道はもうありません。

    $ git log --oneline --all
    e5c0a62 (HEAD -> main) パンを追加
    8b41d07 卵を追加
    3f2a9c1 牛乳を追加
  8. HEAD の reflog は残っています。checkout: moving from menu to main は「menu を離れた」行で、その1つ下の行が、離れる直前に menu でいた場所です。HEAD@{1} の d24492c が「献立を書く」です。

    $ git reflog -3
    e5c0a62 (HEAD -> main) HEAD@{0}: checkout: moving from menu to main
    d24492c HEAD@{1}: commit: 献立を書く
    e5c0a62 HEAD@{2}: checkout: moving from main to menu
  9. そこに、同じ名前の付箋を貼り直します。git branch menu d24492c と ID で書いても同じです。今いる main には触りません。

    $ git branch menu HEAD@{1}
  10. 「献立を書く」が戻りました。-D で剥がしても、コミットは付箋が外れていただけです。

    $ git log --oneline --all
    d24492c (menu) 献立を書く
    e5c0a62 (HEAD -> main) パンを追加
    8b41d07 卵を追加
    3f2a9c1 牛乳を追加

探し方は、そのブランチを離れた行(checkout: moving from menu to …)の1つ下を見るです。離れる直前、HEAD はそのブランチの先頭にいたからです。乗った行(moving from main to menu)の ID は乗った時点の場所なので、そのあとコミットを重ねていれば先頭ではありません。一度も乗らずに剥がしたブランチは HEAD の reflog に出てこないので、was の ID が頼りです。

6. reset のあとで作業を進めてしまったら

1-5 の場面は、reset の直後に気づいたので HEAD@{1} で済みました。気づかないまま次の作業をコミットすると、番号がずれるうえに、reset --hard で戻すと今度は新しいコミットが外れます。

reset のあとで作業を進めてしまったら

  1. 1-5 の reset の回と同じ始まりです。「バターを買う」を1行足します。

    $ echo "バターを買う" >> todo.txt
  2. main が fbb4973 の「バターを追加」に進みました。

    $ git commit -am "バターを追加"
    [main fbb4973] バターを追加
     1 file changed, 1 insertion(+)
  3. 1つだけ戻すつもりが、2つ戻してしまいました。1-5 では、ここですぐ git reset --hard HEAD@{1} で戻しました。今回は気づかずに作業を続けます。

    $ git reset --hard HEAD~2
    HEAD is now at 8b41d07 卵を追加
  4. 新しいファイル notes.txt を作ります。

    $ echo "メモ: 牛乳は低脂肪" > notes.txt
  5. notes.txt をステージに載せます。

    $ git add notes.txt
  6. main は「卵を追加」の上に「メモを作る」を積みました。「パンを追加」と「バターを追加」は薄いままです。

    $ git commit -m "メモを作る"
    [main e672214] メモを作る
     1 file changed, 1 insertion(+)
     create mode 100644 notes.txt
  7. HEAD@{1} はもう「バターを追加」ではありません。そのあとコミットしたので番号が1つずれ、HEAD@{1} は reset の行(8b41d07)になりました。戻したいのは HEAD@{2} の fbb4973 です。

    $ git reflog -3
    e672214 (HEAD -> main) HEAD@{0}: commit: メモを作る
    8b41d07 HEAD@{1}: reset: moving to HEAD~2
    fbb4973 HEAD@{2}: commit: バターを追加
  8. ここで git reset --hard HEAD@{2} と打つと、今度は「メモを作る」が main から外れます。両方残したいので、rescue という付箋を貼ります。main はそのままです。

    $ git branch rescue HEAD@{2}
  9. 「メモを作る」と「バターを追加」の2本が、どちらも付箋から辿れるようになりました。2本をつなぐなら、3-1 の merge か 3-2 の rebase です。

    $ git log --oneline --all
    e672214 (HEAD -> main) メモを作る
    fbb4973 (rescue) バターを追加
    e5c0a62 パンを追加
    8b41d07 卵を追加
    3f2a9c1 牛乳を追加

今のブランチに守りたいコミットがあるときは、reset ではなく branch で付箋を貼ります。2本ができたら、どちらかに寄せるか、3-1 の merge や 3-2 の rebase でつなぎます。

7. reflog でも取り戻せないもの

reflog が覚えているのは HEAD が指したコミットだけです。次の3つは取り戻せません。

一度もコミットしていない書きかけ

1-5 の reset --hard も、1-4 の git checkout -- ファイル も、コミットしていない変更を消します。消えたあとで reflog を見ても、何も見つかりません。

reflog にも残らないもの — コミットしていない書きかけ

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

    $ echo "バターを買う" >> todo.txt
  2. コミットしました。コミットになったものは、reflog に記録されます。

    $ git commit -am "バターを追加"
    [main fbb4973] バターを追加
     1 file changed, 1 insertion(+)
  3. 続けて「チーズを買う」を書き足します。まだコミットしていない書きかけです。

    $ echo "チーズを買う" >> todo.txt
  4. 行き先を書かない git reset --hard は、付箋を動かさずに、ステージと作業ツリーを HEAD の中身で上書きします。

    $ git reset --hard
    HEAD is now at fbb4973 バターを追加
  5. 「チーズを買う」は消えました。

    $ cat todo.txt
    牛乳を買う
    卵を買う
    パンを買う
    バターを買う
  6. 増えたのは reset: moving to HEAD の1行だけで、指しているのは「バターを追加」と同じ fbb4973 です。reflog はコミットの記録なので、書きかけの「チーズを買う」はどの行にもありません。

    $ git reflog -2
    fbb4973 (HEAD -> main) HEAD@{0}: reset: moving to HEAD
    fbb4973 HEAD@{1}: commit: バターを追加
  7. もう一度、書きかけの「ジャムを買う」を足します。

    $ echo "ジャムを買う" >> todo.txt
  8. git checkout -- ファイル は、そのファイルの書きかけを捨てます(1-4 の回)。HEAD は動きません。

    $ git checkout -- todo.txt
    Updated 1 path from the index
  9. 今度は reflog に1行も増えていません。HEAD が動かない操作は記録されないので、消えた書きかけを探す手がかりはどこにも残りません。

    $ git reflog -2
    fbb4973 (HEAD -> main) HEAD@{0}: reset: moving to HEAD
    fbb4973 HEAD@{1}: commit: バターを追加

書きかけを守るには、消える操作の前にコミットするか、1-7 stashで退避しておくしかありません。小さな「作業中」のコミットでもかまいません。コミットになっていれば、あとで付箋から外しても reflog から拾えます。

ほかの PC の記録

reflog は手元の PC の、そのリポジトリの中だけの記録で、push しても GitHub には送られません。3-3 force pushで Windows から force push したせいで Mac のコミットが GitHub から外れたなら、見に行く先は Mac です。Mac の main がまだそのコミットを指しているか、Mac の git reflog に残っています。force push の前に Windows で fetch していたなら、Windows の git reflog show origin/main(追跡ブランチの reflog。この教材の画面では再現していません)にも残っています。

時間がたったもの

Git はときどき自動で片付け(git gc)をし、古い記録を消します。既定では reflog の行はおよそ90日、付箋から外れたコミットを指す行は30日で消され、そのあとコミット本体も掃除されます。取り戻すなら、気づいたその日に付箋を貼るのが確実です。

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

コマンド何が起きるか消えうるもの
git reflog / git reflog -10HEAD が通った場所を見るだけなし
git branch 名前 HEAD@{n}見つけたコミットに付箋を1枚足す。今のブランチ・作業ツリーには触らないなし(同じ名前の付箋があれば止まる)
git reset --hard HEAD@{n}今のブランチの付箋を動かし、ステージと作業ツリーも上書きするコミットしていない書きかけ。それまでの先頭は付箋から外れる(reflog に残る)
git branch -D 名前確認せずに付箋を剥がす。そのブランチの reflog も消える注意 そのブランチにしか無いコミットへの道筋(HEAD の reflog が残るあいだは戻せる)

見分け方は、付箋を足すだけか、今のブランチの付箋を動かすかです。取り戻しの場面で git reset --hard HEAD@{n} を求められたら、先に git status を見て、迷うなら「まず git branch で付箋を貼って」と頼み直してください。

まとめ

  • git reflog は HEAD が通った場所の記録。付箋から外れたコミットも、reflog の行が指しているあいだは消えない
  • 1行は「操作のあとで HEAD がいた ID」「何回前か(HEAD@{n})」「何をしたか」。番号は操作のたびにずれる
  • git branch 名前 HEAD@{n} は付箋を足すだけで安全。git reset --hard HEAD@{n} は今のブランチごと戻し、書きかけを消す
  • rebase の取り消しは rebase (start) の1つ下。-D で剥がしたブランチは was の ID か、そのブランチを離れた行の1つ下
  • コミットしていない書きかけ、ほかの PC の記録、およそ90日(外れたものは30日)を過ぎたものは取り戻せない

第3部では、歴史をつなぐ merge、作り直す rebase、GitHub の付箋を付け替える force push を見て、最後に取り戻し方を見ました。merge と rebase は途中なら --abort、終わったあとは reflog。force push で外れたコミットは、それを持つ PC の手元か reflog です。コミットになってさえいれば、ほとんど取り戻せます。許可画面で迷ったら、「書きかけが消えるか」と「GitHub の付箋が付け替わるか」の2点を見る。これが第3部で身につけたい見分け方です。

次の第4部では、Claude Code そのものに目を向けます。変更の確認 → ステージ → コミット → push の流れで Claude Code が打つコマンドを読み、ここまでの全コマンドを許可画面の「安全・注意・危険」の3段で整理し直します。最後に、複数の作業を並べて進める worktree を見ます。