git reset --hard を打った直後に「今の変更、まだ残しておきたかった」と気づいたり、マージ済みだと思って消したブランチが実はまだ必要だったり。Git を使っていると、こうした「消してしまった」場面にときどき出くわします。こんなときに頼りになるのが git reflog です。この記事では、reflog が何を記録しているのか、出力をどう読むのか、そして消したコミットやブランチを実際に復元する手順を順番に解説します。
目次
git reflog は「HEAD が動いた足跡」の記録
reflog は reference log(参照ログ)の略で、HEAD やブランチがこれまでどのコミットを指してきたかを時系列で記録したものです。コミットを作ったとき、ブランチを切り替えたとき、reset や rebase や merge をしたとき、つまり「指し先が動いたとき」に1行ずつ追記されていきます。記録は .git/logs/ の下にテキストファイルとして保存されており、あなたのローカルリポジトリだけが持っている情報です。
使い方はとても簡単で、リポジトリの中で次のコマンドを打つだけです。
git reflog
git log との違い
git log は、今いるブランチの先端から親をたどって表示する「コミットの系譜」です。そのため reset で切り捨てたコミットや、削除したブランチの上にあったコミットは、どこからも到達できなくなった時点で git log には一切出てきません。
一方 reflog は系譜ではなく操作の履歴です。「何時何分に HEAD はこのハッシュを指していた」という事実がそのまま残るので、捨てたはずのコミットのハッシュもしばらくは残り続けます。Git はコミットを作った時点でオブジェクトとしてリポジトリに保存しているため、ハッシュさえ分かればそこへ戻れる、というのが復元の基本的な仕組みです。
出力の読み方
実際に git reflog を打つと、次のような出力が返ってきます。
$ git reflog
7c21ab9 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~2
4d8e0f1 HEAD@{1}: commit: フォームのバリデーションを追加
b93c517 HEAD@{2}: commit: ヘッダーのリンク先を修正
7c21ab9 (HEAD -> main) HEAD@{3}: checkout: moving from feature/form to main
a05f3d8 HEAD@{4}: commit: 入力欄のスタイルを調整
7c21ab9 HEAD@{5}: checkout: moving from main to feature/form
行の先頭がそのときのコミットハッシュ、続く HEAD@{0} のような表記が何回前の位置かを表す番号です。HEAD@{0} が現在地、HEAD@{1} が1つ前の位置、HEAD@{2} がその前という具合に、新しいものから順に並びます。番号は操作のたびにずれていくので、コマンドを打つ前に必ず git reflog を見て今の番号を確認してください。
ここで注意したいのが、HEAD@{2} と HEAD~2 はまったく別物だという点です。HEAD~2 は「現在のコミットから親を2つたどった先」を指しますが、HEAD@{2} は「2回前の操作のとき HEAD が指していた場所」を指します。上の例で HEAD@{3} と HEAD@{5} が同じ 7c21ab9 になっているのは、別々のタイミングで同じコミットを指していたからです。
コロンのあとに書かれているのが操作の種類です。よく見かけるものをまとめます。
| 表記 | 意味 |
|---|---|
commit | コミットを作成した |
commit (amend) | --amend でコミットをやり直した |
commit (initial) | リポジトリの最初のコミットを作成した |
checkout | ブランチやコミットへ移動した(git switch も同じ表記) |
reset | git reset で HEAD を移動した |
merge | マージによって HEAD が進んだ |
rebase | rebase の実行中・完了時に HEAD が動いた |
pull | git pull でリモートの内容を取り込んだ |
clone | clone した直後の初期位置 |
「何をしたときの状態か」はこの表記と、commit: のあとに続くコミットメッセージから判断します。git reflog --date=iso のように --date を付けると、番号の代わりに HEAD@{2026-09-22 14:03:11 +0900} のような日時表記になり、「たしか昼過ぎまでは動いていた」という記憶から探すときに便利です。
実践1: git reset –hard で消した変更を戻す
もっとも出番が多いのがこのケースです。先ほどの出力例では HEAD@{0} が reset: moving to HEAD~2 になっており、直前に git reset --hard HEAD~2 を実行してコミットを2つ切り捨てたことが分かります。
戻したいのは reset を実行する直前の状態、つまり HEAD@{1} です。次のコマンドで、その位置までブランチごと巻き戻せます。
# reset する直前の位置に戻す
git reset --hard HEAD@{1}
# ハッシュを直接指定してもよい(こちらのほうが安全)
git reset --hard 4d8e0f1
番号は操作のたびにずれるため、git reflog で場所を確認したあとに別のコマンドを挟んでしまうと HEAD@{1} の指す先が変わってしまいます。慣れないうちは、reflog で見つけたハッシュをそのままコピーして指定するほうが確実です。
戻る前に中身を確認したいときは、git show 4d8e0f1 で差分を見ておくと失敗が減ります。
ひとつだけ覚えておきたいのは、reflog で救えるのはコミットしてあった内容だけということです。一度もコミットしていない作業ツリーの変更や、git add すらしていないファイルは reflog に記録されないため、git reset --hard で消えたら戻せません。
実践2: 削除したブランチを復元する
git branch -D feature/form のように強制削除したブランチも、コミット自体はリポジトリに残っています。必要なのは、そのブランチが最後に指していたコミットのハッシュです。
git reflog を開き、そのブランチで作業していた最後のコミット、または checkout: moving from feature/form to main のようにブランチから離れた行を探します。この行の左端のハッシュが、離れたあとの位置(移動先)である点に注意してください。ブランチの先端は、その1つ上の行(feature/form にいたときの最後の commit 行)のハッシュです。
ハッシュが分かったら、そのコミットを指す新しいブランチを作れば復活します。
# ブランチを作って、そのまま移動する
git checkout -b feature/form a05f3d8
# 今の作業を中断したくないときは、作るだけにする
git branch feature/form a05f3d8
ブランチの実体は「コミットを指す名前」でしかないので、同じコミットを指す名前を作り直せば、削除前とまったく同じ状態が戻ってきます。ブランチ名は元と違っていても構いません。git switch -c feature/form a05f3d8 と書いても同じ結果になります。
実践3: rebase を失敗したあと元の履歴に戻る
git rebase -i でコミットをまとめたり並べ替えたりしたあとに、「順番を間違えた」「必要なコミットを drop してしまった」と気づくことがあります。rebase はコミットを作り直す操作なので、元の履歴はどこからも参照されない状態になりますが、reflog には rebase を始める前の位置がきちんと残っています。
$ git reflog
f01a9c3 (HEAD -> feature/form) HEAD@{0}: rebase (finish): returning to refs/heads/feature/form
f01a9c3 (HEAD -> feature/form) HEAD@{1}: rebase (squash): 入力フォームを実装
6ad4b72 HEAD@{2}: rebase (start): checkout main
a05f3d8 HEAD@{3}: commit: 入力欄のスタイルを調整
ここで目印になるのが rebase (start) の行です。この行は rebase の土台へ移動した記録なので、その1つ下の行が rebase を始める直前のブランチの先端になります。上の例なら HEAD@{3} の a05f3d8 がそれにあたるので、次のように戻します。
git reset --hard a05f3d8
rebase の途中で「やっぱりやめたい」と思っただけなら、reflog を使わずに git rebase --abort を実行すれば元の状態に戻ります。reflog の出番は、rebase を最後まで完了させてしまったあとです。
なお、rebase の直前の位置は ORIG_HEAD という名前でも参照できます(reset や merge でも同様に更新されます)。直近の1回を取り消すだけなら git reset --hard ORIG_HEAD が手軽ですが、次の大きな操作で上書きされてしまうため、確実なのは reflog でハッシュを確認する方法です。
ブランチ単位の reflog を見る
引数なしの git reflog は HEAD の履歴を表示します。HEAD はブランチを行き来するたびに動くので、行数が多くなると目的の行を探しにくくなります。そんなときは、ブランチ名を指定してそのブランチ自身の reflog を見ます。
$ git reflog show main
7c21ab9 main@{0}: reset: moving to HEAD~2
4d8e0f1 main@{1}: commit: フォームのバリデーションを追加
b93c517 main@{2}: commit: ヘッダーのリンク先を修正
2fe6b04 main@{3}: pull: Fast-forward
こちらは main というブランチが指してきた位置だけが並ぶので、単純に「このブランチをどこまで巻き戻したいか」を選びやすくなります。表記も main@{0} のようにブランチ名付きになり、そのまま git reset --hard main@{1} のように指定できます。「昨日の時点の main」を見たい場合は git show main@{yesterday} のように時刻で指定することもできます。
reflog はいつまで残るのか
reflog は永久に残るものではありません。git gc(不要なオブジェクトの掃除)が走るときに、古いエントリが期限にしたがって削除されます。期限は2つの設定で決まっており、既定値は次のとおりです。
| 設定 | 対象と既定値 |
|---|---|
gc.reflogExpire | 今の履歴から到達できるエントリ。既定は90日 |
gc.reflogExpireUnreachable | どこからも到達できなくなったコミットのエントリ。既定は30日 |
復元したいコミットは多くの場合「到達できないコミット」なので、実質的な猶予は30日程度と考えておくとよいでしょう。消したことに気づいたら、なるべく早く reflog を確認するのが安全です。
もうひとつ大切なのが、reflog はローカル専用の記録だという点です。git push や git clone で共有されることはないので、clone してきたばかりのリポジトリには clone の1行しかありませんし、別のマシンや同僚の環境で行われた操作を自分の reflog から探すこともできません。「あの操作は別のPCでやった」という場合は、そのPCで git reflog を実行する必要があります。
reflog に目的のコミットが見つからないとき
reflog をひととおり眺めても目当ての行が無い、というときは、次の順に確かめてみてください。
そもそもコミットしていなかった
前述のとおり、reflog に残るのはコミットとして保存された内容だけです。ただし git add までは済ませてあった変更なら、インデックスに登録された時点で Git のオブジェクトとして保存されているため、後述の git fsck で見つかる可能性があります。エディタのローカル履歴やバックアップを先に探したほうが早いこともあります。
別のリポジトリやサブモジュールで操作していた
reflog は .git ディレクトリごとに独立しています。サブモジュールの中での操作は親リポジトリの reflog には出てこないため、そのディレクトリに移動して確認してください。
git fsck で迷子のコミットを探す
reflog のエントリが期限切れで消えていても、コミットのオブジェクト自体がまだ残っていることがあります。どこからも参照されていないオブジェクトは git fsck で洗い出せます。
$ git fsck --lost-found
Checking object directories: 100% (256/256), done.
dangling commit a05f3d8c0e4b1f27d9ae35c6b0a7d21f3c8e9b40
dangling blob 5e1c7a93b28d640fa1c05e37b9d2a84c6f03e71d
dangling commit と表示されたハッシュを git show や git log で確認し、目的のものが見つかったら git branch rescue <ハッシュ> でブランチを付けて救出します。--lost-found を付けると、見つかったオブジェクトが .git/lost-found/ 以下にファイルとして書き出されるので、中身を直接開いて確認することもできます。dangling blob はコミットされていないファイル単体なので、git add だけして失った変更はここから拾える場合があります。
ただし git gc が実行されたあとでは、これらのオブジェクトも削除されている可能性があります。復元の見込みがあるうちは、むやみに git gc や git prune を実行しないようにしてください。
まとめ
git reflog は、HEAD やブランチが指してきた位置を時系列で記録したもので、git log には現れない「捨てたコミット」までたどれるのが最大の特徴です。出力の HEAD@{0} は現在地、番号が増えるほど過去の位置を表し、commit や reset といった操作名から「どの時点に戻りたいか」を判断します。git reset --hard で消した変更は git reset --hard HEAD@{1} で、削除したブランチは git checkout -b <ブランチ名> <ハッシュ> で、失敗した rebase は rebase (start) の1つ下の行のハッシュへ戻すことで復元できます。番号はずれやすいので、実際に指定するときはハッシュをコピーするのが確実です。到達できなくなったコミットの記録は既定で30日ほどで消え、reflog 自体も自分のマシンにしか存在しません。「消してしまった」と思ったら、まず git reflog を開く習慣をつけておきましょう。